Threading for renderer, textures, entities, game state

This commit is contained in:
2026-03-11 09:01:10 +01:00
parent 755e14ad62
commit d748ca63a0
12 changed files with 218 additions and 30 deletions

View File

@@ -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)

View File

@@ -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
View 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:
};
}

View File

@@ -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;
}; };
} }

View 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;
};
}

View 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;
};
}

View File

@@ -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() \
};

View File

@@ -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;
}; };
} }

View File

@@ -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
View 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
View 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;
}
}
}

View File

@@ -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();
} }
} }
} }