Threading for renderer, textures, entities, game state
This commit is contained in:
@@ -7,11 +7,18 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
# Include FetchContent to download SDL from source
|
# Include FetchContent to download SDL from source
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
# Download SDL2 from source
|
# Download SDL3 from source
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
SDL2
|
SDL3
|
||||||
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
|
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
|
||||||
GIT_TAG release-2.30.3
|
GIT_TAG release-3.4.2
|
||||||
|
)
|
||||||
|
|
||||||
|
# Download SDL3_image from source
|
||||||
|
FetchContent_Declare(
|
||||||
|
SDL3_image
|
||||||
|
GIT_REPOSITORY https://github.com/libsdl-org/SDL_image.git
|
||||||
|
GIT_TAG release-3.2.4
|
||||||
)
|
)
|
||||||
|
|
||||||
# Work around PipeWire API mismatch on some Linux distributions.
|
# Work around PipeWire API mismatch on some Linux distributions.
|
||||||
@@ -19,8 +26,9 @@ FetchContent_Declare(
|
|||||||
set(SDL_PIPEWIRE OFF CACHE BOOL "Disable SDL PipeWire backend" FORCE)
|
set(SDL_PIPEWIRE OFF CACHE BOOL "Disable SDL PipeWire backend" FORCE)
|
||||||
set(SDL_PIPEWIRE_SHARED OFF CACHE BOOL "Disable dynamic PipeWire loading in SDL" FORCE)
|
set(SDL_PIPEWIRE_SHARED OFF CACHE BOOL "Disable dynamic PipeWire loading in SDL" FORCE)
|
||||||
|
|
||||||
# Make SDL available
|
# Make SDL libraries available
|
||||||
FetchContent_MakeAvailable(SDL2)
|
FetchContent_MakeAvailable(SDL3)
|
||||||
|
FetchContent_MakeAvailable(SDL3_image)
|
||||||
|
|
||||||
# Collect all source files from src/ and nested directories
|
# Collect all source files from src/ and nested directories
|
||||||
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp")
|
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp")
|
||||||
@@ -31,5 +39,5 @@ add_executable(${PROJECT_NAME} ${SOURCES})
|
|||||||
# Include headers from include/
|
# Include headers from include/
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE include)
|
target_include_directories(${PROJECT_NAME} PRIVATE include)
|
||||||
|
|
||||||
# Link SDL2 to the executable
|
# Link SDL3 and SDL3_image to the executable
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE SDL2::SDL2)
|
target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3 SDL3_image::SDL3_image)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Končni Projekt za 3. Letnik - PRAP
|
# Končni Projekt za 3. Letnik - PRAP
|
||||||
|
|
||||||
## Uporabljene knjižnice
|
## Uporabljene knjižnice
|
||||||
- SDL2
|
- SDL3
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
gcc compiler + cmake
|
gcc compiler + cmake
|
||||||
|
|||||||
25
include/object/entity.hpp
Normal file
25
include/object/entity.hpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <utils.hpp>
|
||||||
|
|
||||||
|
namespace Game::Renderer {
|
||||||
|
class Texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Game::Object {
|
||||||
|
class Entity {
|
||||||
|
public:
|
||||||
|
Entity(std::string& name, Game::Renderer::Texture* tex) : mName(name), mTex(tex) {}
|
||||||
|
// I will define the copy and move constructors later - just deleted for now
|
||||||
|
DISABLE_COPY_AND_MOVE(Entity);
|
||||||
|
~Entity();
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void update();
|
||||||
|
protected:
|
||||||
|
std::string mName;
|
||||||
|
Game::Renderer::Texture* mTex;
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,19 +1,27 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <utils.hpp>
|
||||||
|
|
||||||
namespace Game::Renderer {
|
namespace Game::Renderer {
|
||||||
class Renderer {
|
class Renderer {
|
||||||
public:
|
public:
|
||||||
Renderer();
|
Renderer();
|
||||||
|
DISABLE_COPY_AND_MOVE(Renderer);
|
||||||
~Renderer();
|
~Renderer();
|
||||||
|
|
||||||
bool init(SDL_Window* window);
|
bool init(SDL_Window* window);
|
||||||
void clear();
|
void run(std::stop_token stoken);
|
||||||
void present();
|
void destroy();
|
||||||
|
|
||||||
|
SDL_Renderer* getSDLRenderer() { return mRenderer; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void mClear();
|
||||||
|
void mPresent();
|
||||||
|
|
||||||
SDL_Renderer* mRenderer;
|
SDL_Renderer* mRenderer;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
21
include/renderer/texture.hpp
Normal file
21
include/renderer/texture.hpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3_image/SDL_image.h>
|
||||||
|
#include <string>
|
||||||
|
#include <utils.hpp>
|
||||||
|
#include <renderer/renderer.hpp>
|
||||||
|
|
||||||
|
namespace Game::Renderer {
|
||||||
|
class Texture {
|
||||||
|
public:
|
||||||
|
Texture(std::string& path, Renderer* renderer, std::string id = "noname");
|
||||||
|
Texture(const Texture&);
|
||||||
|
Texture& operator=(const Texture&);
|
||||||
|
DISABLE_MOVE(Texture);
|
||||||
|
~Texture();
|
||||||
|
private:
|
||||||
|
SDL_Texture* mTex;
|
||||||
|
std::string mId;
|
||||||
|
};
|
||||||
|
}
|
||||||
24
include/state/gamestate.hpp
Normal file
24
include/state/gamestate.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <vector>
|
||||||
|
#include <utils.hpp>
|
||||||
|
#include <object/entity.hpp>
|
||||||
|
|
||||||
|
namespace Game::State {
|
||||||
|
class GameState {
|
||||||
|
public:
|
||||||
|
static GameState& getInstance() { static GameState instance; return instance; }
|
||||||
|
|
||||||
|
// Retrieve a COPY of the entities - Only used by renderer, use getAtIndex() for generic access
|
||||||
|
const std::vector<Object::Entity>& getEntities();
|
||||||
|
// Retrieve a REFERENCE of the entities; DANGEROUS!
|
||||||
|
std::vector<Object::Entity>* getEntitiesRef();
|
||||||
|
// Update entity at index, by REFERENCE
|
||||||
|
Object::Entity* getAtIndex(size_t at);
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable std::shared_mutex mMutex;
|
||||||
|
std::vector<Object::Entity> mEntities;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -19,4 +19,13 @@
|
|||||||
std::cout << "\033[33m[WARN] " << __PRETTY_FUNCTION__ << ' ' << Msg << "\033[0m\n";
|
std::cout << "\033[33m[WARN] " << __PRETTY_FUNCTION__ << ' ' << Msg << "\033[0m\n";
|
||||||
|
|
||||||
#define ERROR(Msg) \
|
#define ERROR(Msg) \
|
||||||
std::cout << "\033[31m[ERROR] " << __PRETTY_FUNCTION__ << ' ' << Msg << '\033[0m\n';
|
std::cout << "\033[31m[ERROR] " << __PRETTY_FUNCTION__ << ' ' << Msg << "\033[0m\n";
|
||||||
|
|
||||||
|
#define GAME_ENTITY(ClassName) \
|
||||||
|
class ClassName : public Object::Entity { \
|
||||||
|
using Object::Entity::Entity; // Inherit constructors \
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#define END_GAME_ENTITY() \
|
||||||
|
};
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <utils.hpp>
|
#include <utils.hpp>
|
||||||
#include <renderer/renderer.hpp>
|
#include <renderer/renderer.hpp>
|
||||||
|
#include <thread>
|
||||||
|
#include <functional>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
namespace Game::Window {
|
namespace Game::Window {
|
||||||
class Window {
|
class Window {
|
||||||
@@ -20,6 +23,7 @@ namespace Game::Window {
|
|||||||
private:
|
private:
|
||||||
SDL_Window* mWindow;
|
SDL_Window* mWindow;
|
||||||
Renderer::Renderer mRenderer;
|
Renderer::Renderer mRenderer;
|
||||||
|
std::jthread mRenderThread;
|
||||||
bool mRunning;
|
bool mRunning;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -5,31 +5,52 @@ namespace Game::Renderer {
|
|||||||
Renderer::Renderer() : mRenderer(nullptr) {}
|
Renderer::Renderer() : mRenderer(nullptr) {}
|
||||||
|
|
||||||
Renderer::~Renderer() {
|
Renderer::~Renderer() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::destroy() {
|
||||||
if (mRenderer) {
|
if (mRenderer) {
|
||||||
SDL_DestroyRenderer(mRenderer);
|
SDL_DestroyRenderer(mRenderer);
|
||||||
|
mRenderer = nullptr;
|
||||||
|
LOG("Destroyed Renderer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Renderer::init(SDL_Window* window) {
|
bool Renderer::init(SDL_Window* window) {
|
||||||
mRenderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
mRenderer = SDL_CreateRenderer(window, nullptr);
|
||||||
if (!mRenderer) {
|
if (!mRenderer) {
|
||||||
std::string errorMsg = "Failed to create renderer: " + std::string(SDL_GetError());
|
std::string errorMsg = "Failed to create renderer: " + std::string(SDL_GetError());
|
||||||
ERROR(errorMsg.c_str());
|
ERROR(errorMsg.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(mRenderer, 0, 0, 255, 255); // Temp
|
if (!SDL_SetRenderDrawColor(mRenderer, 0, 0, 255, 255)) {
|
||||||
|
ERROR("Failed to set renderer draw color: " << SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
LOG("Renderer created successfully");
|
LOG("Renderer created successfully");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::clear() {
|
void Renderer::run(std::stop_token stoken) {
|
||||||
SDL_RenderClear(mRenderer);
|
while (!stoken.stop_requested()) {
|
||||||
|
mClear();
|
||||||
|
// Get gamestate mutex and render the objects here; GameState::getState().objects or something, idk
|
||||||
|
mPresent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::present() {
|
void Renderer::mClear() {
|
||||||
SDL_RenderPresent(mRenderer);
|
if (!SDL_RenderClear(mRenderer)) {
|
||||||
|
ERROR("Failed to clear renderer: " << SDL_GetError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::mPresent() {
|
||||||
|
if (!SDL_RenderPresent(mRenderer)) {
|
||||||
|
ERROR("Failed to present renderer: " << SDL_GetError());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
33
src/renderer/texture.cpp
Normal file
33
src/renderer/texture.cpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#include <renderer/texture.hpp>
|
||||||
|
|
||||||
|
namespace Game::Renderer {
|
||||||
|
Texture::Texture(std::string& path, Renderer* renderer, std::string id)
|
||||||
|
: mTex(nullptr), mId(id) {
|
||||||
|
SDL_Surface* surf = IMG_Load(path.c_str());
|
||||||
|
if (!surf) {
|
||||||
|
ERROR("Failed to load image at " << path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mTex = SDL_CreateTextureFromSurface(renderer->getSDLRenderer(), surf);
|
||||||
|
SDL_DestroySurface(surf);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture::Texture(const Texture& other) {
|
||||||
|
// Copy the references, since copying memory would require re-initing a bunch of things - for now
|
||||||
|
this->mTex = other.mTex;
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture& Texture::operator=(const Texture& other) {
|
||||||
|
// Same reasoning
|
||||||
|
this->mTex = other.mTex;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture::~Texture() {
|
||||||
|
if (mTex)
|
||||||
|
SDL_DestroyTexture(mTex);
|
||||||
|
LOG("Destroyed texture '" << mId << "'")
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/state/gamestate.cpp
Normal file
25
src/state/gamestate.cpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#include <state/gamestate.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace Game::State {
|
||||||
|
// TODO: Caller should also hold these locks
|
||||||
|
const std::vector<Object::Entity>& GameState::getEntities() {
|
||||||
|
std::shared_lock lock(mMutex);
|
||||||
|
return mEntities;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Object::Entity>* GameState::getEntitiesRef() {
|
||||||
|
std::shared_lock lock(mMutex);
|
||||||
|
return &mEntities;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object::Entity* GameState::getAtIndex(size_t at) {
|
||||||
|
std::shared_lock lock(mMutex);
|
||||||
|
try {
|
||||||
|
return &mEntities.at(at);
|
||||||
|
} catch (const std::out_of_range& e) {
|
||||||
|
WARN("Tried to access entity from GameState out of range!");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,33 @@
|
|||||||
#include <window/window.hpp>
|
#include <window/window.hpp>
|
||||||
|
|
||||||
namespace Game::Window {
|
namespace Game::Window {
|
||||||
Window::Window() : mWindow(nullptr), mRunning(false), mRenderer() {}
|
Window::Window() : mWindow(nullptr), mRunning(false) {
|
||||||
|
}
|
||||||
|
|
||||||
Window::~Window() {
|
Window::~Window() {
|
||||||
|
// Stop render thread
|
||||||
|
if (mRenderThread.joinable()) {
|
||||||
|
mRenderThread.request_stop();
|
||||||
|
mRenderThread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
mRenderer.destroy();
|
||||||
|
|
||||||
if (mWindow) {
|
if (mWindow) {
|
||||||
SDL_DestroyWindow(mWindow);
|
SDL_DestroyWindow(mWindow);
|
||||||
mWindow = nullptr;
|
mWindow = nullptr;
|
||||||
mRunning = false;
|
|
||||||
LOG("Window destroyed successfully");
|
LOG("Window destroyed successfully");
|
||||||
SDL_Quit();
|
|
||||||
}
|
}
|
||||||
|
SDL_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Window::init(int width, int height, const std::string& title) {
|
bool Window::init(int width, int height, const std::string& title) {
|
||||||
if (SDL_Init(SDL_INIT_VIDEO) < 0) { return false; }
|
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||||
mWindow = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN);
|
ERROR("Failed to initialize SDL: " << SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mWindow = SDL_CreateWindow(title.c_str(), width, height, SDL_WINDOW_RESIZABLE);
|
||||||
if (!mWindow) {
|
if (!mWindow) {
|
||||||
ERROR("Failed to create window: " << SDL_GetError());
|
ERROR("Failed to create window: " << SDL_GetError());
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
@@ -24,12 +36,14 @@ namespace Game::Window {
|
|||||||
|
|
||||||
LOG("Window created successfully");
|
LOG("Window created successfully");
|
||||||
|
|
||||||
|
// Spawn new thread for renderer
|
||||||
if (!mRenderer.init(mWindow)) {
|
if (!mRenderer.init(mWindow)) {
|
||||||
SDL_DestroyWindow(mWindow);
|
SDL_DestroyWindow(mWindow);
|
||||||
mWindow = nullptr;
|
mWindow = nullptr;
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
mRenderThread = std::jthread(std::bind_front(&Renderer::Renderer::run, &mRenderer));
|
||||||
|
|
||||||
mRunning = true;
|
mRunning = true;
|
||||||
|
|
||||||
@@ -40,16 +54,12 @@ namespace Game::Window {
|
|||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (mRunning) {
|
while (mRunning) {
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
if (event.type == SDL_QUIT) {
|
if (event.type == SDL_EVENT_QUIT) {
|
||||||
mRunning = false;
|
mRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle other events (e.g., keyboard, mouse) here
|
// Handle other events (e.g., keyboard, mouse) here
|
||||||
}
|
}
|
||||||
|
|
||||||
mRenderer.clear();
|
|
||||||
// Render game objects here
|
|
||||||
mRenderer.present();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user