diff --git a/CMakeLists.txt b/CMakeLists.txt index 45933a5..952832b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,11 +7,18 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # Include FetchContent to download SDL from source include(FetchContent) -# Download SDL2 from source +# Download SDL3 from source FetchContent_Declare( - SDL2 + SDL3 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. @@ -19,8 +26,9 @@ FetchContent_Declare( 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) -# Make SDL available -FetchContent_MakeAvailable(SDL2) +# Make SDL libraries available +FetchContent_MakeAvailable(SDL3) +FetchContent_MakeAvailable(SDL3_image) # Collect all source files from src/ and nested directories file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp") @@ -31,5 +39,5 @@ add_executable(${PROJECT_NAME} ${SOURCES}) # Include headers from include/ target_include_directories(${PROJECT_NAME} PRIVATE include) -# Link SDL2 to the executable -target_link_libraries(${PROJECT_NAME} PRIVATE SDL2::SDL2) +# Link SDL3 and SDL3_image to the executable +target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3 SDL3_image::SDL3_image) diff --git a/README.md b/README.md index e1015dd..99e63c4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Končni Projekt za 3. Letnik - PRAP ## Uporabljene knjižnice -- SDL2 +- SDL3 ## Build gcc compiler + cmake diff --git a/include/object/entity.hpp b/include/object/entity.hpp new file mode 100644 index 0000000..822e6be --- /dev/null +++ b/include/object/entity.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +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: + }; +} \ No newline at end of file diff --git a/include/renderer/renderer.hpp b/include/renderer/renderer.hpp index ac76fe3..f46b4d1 100644 --- a/include/renderer/renderer.hpp +++ b/include/renderer/renderer.hpp @@ -1,19 +1,27 @@ #pragma once -#include +#include #include +#include +#include namespace Game::Renderer { class Renderer { public: Renderer(); + DISABLE_COPY_AND_MOVE(Renderer); ~Renderer(); bool init(SDL_Window* window); - void clear(); - void present(); + void run(std::stop_token stoken); + void destroy(); + + SDL_Renderer* getSDLRenderer() { return mRenderer; } private: + void mClear(); + void mPresent(); + SDL_Renderer* mRenderer; }; } \ No newline at end of file diff --git a/include/renderer/texture.hpp b/include/renderer/texture.hpp new file mode 100644 index 0000000..56b0df6 --- /dev/null +++ b/include/renderer/texture.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include +#include +#include + +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; + }; +} \ No newline at end of file diff --git a/include/state/gamestate.hpp b/include/state/gamestate.hpp new file mode 100644 index 0000000..aa8439b --- /dev/null +++ b/include/state/gamestate.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include + +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& getEntities(); + // Retrieve a REFERENCE of the entities; DANGEROUS! + std::vector* getEntitiesRef(); + // Update entity at index, by REFERENCE + Object::Entity* getAtIndex(size_t at); + + private: + mutable std::shared_mutex mMutex; + std::vector mEntities; + }; +} \ No newline at end of file diff --git a/include/utils.hpp b/include/utils.hpp index d382c32..2f9d794 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -19,4 +19,13 @@ std::cout << "\033[33m[WARN] " << __PRETTY_FUNCTION__ << ' ' << Msg << "\033[0m\n"; #define ERROR(Msg) \ - std::cout << "\033[31m[ERROR] " << __PRETTY_FUNCTION__ << ' ' << Msg << '\033[0m\n'; \ No newline at end of file + 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() \ + }; \ No newline at end of file diff --git a/include/window/window.hpp b/include/window/window.hpp index 8146736..06e6784 100644 --- a/include/window/window.hpp +++ b/include/window/window.hpp @@ -1,11 +1,14 @@ #pragma once #include -#include +#include #include #include #include +#include +#include +#include namespace Game::Window { class Window { @@ -18,8 +21,9 @@ namespace Game::Window { void run(); private: - SDL_Window *mWindow; + SDL_Window* mWindow; Renderer::Renderer mRenderer; + std::jthread mRenderThread; bool mRunning; }; } \ No newline at end of file diff --git a/src/renderer/renderer.cpp b/src/renderer/renderer.cpp index d044fd1..6a3a47f 100644 --- a/src/renderer/renderer.cpp +++ b/src/renderer/renderer.cpp @@ -5,31 +5,52 @@ namespace Game::Renderer { Renderer::Renderer() : mRenderer(nullptr) {} Renderer::~Renderer() { + destroy(); + } + + void Renderer::destroy() { if (mRenderer) { SDL_DestroyRenderer(mRenderer); + mRenderer = nullptr; + LOG("Destroyed Renderer"); } } bool Renderer::init(SDL_Window* window) { - mRenderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + mRenderer = SDL_CreateRenderer(window, nullptr); if (!mRenderer) { std::string errorMsg = "Failed to create renderer: " + std::string(SDL_GetError()); ERROR(errorMsg.c_str()); 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"); return true; } - void Renderer::clear() { - SDL_RenderClear(mRenderer); + void Renderer::run(std::stop_token stoken) { + while (!stoken.stop_requested()) { + mClear(); + // Get gamestate mutex and render the objects here; GameState::getState().objects or something, idk + mPresent(); + } } - void Renderer::present() { - SDL_RenderPresent(mRenderer); + void Renderer::mClear() { + 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()); + } } } \ No newline at end of file diff --git a/src/renderer/texture.cpp b/src/renderer/texture.cpp new file mode 100644 index 0000000..39e31d5 --- /dev/null +++ b/src/renderer/texture.cpp @@ -0,0 +1,33 @@ +#include + +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 << "'") + } +} \ No newline at end of file diff --git a/src/state/gamestate.cpp b/src/state/gamestate.cpp new file mode 100644 index 0000000..810174d --- /dev/null +++ b/src/state/gamestate.cpp @@ -0,0 +1,25 @@ +#include +#include + +namespace Game::State { + // TODO: Caller should also hold these locks + const std::vector& GameState::getEntities() { + std::shared_lock lock(mMutex); + return mEntities; + } + + std::vector* 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; + } + } +} \ No newline at end of file diff --git a/src/window/window.cpp b/src/window/window.cpp index 4f90e77..0e189f9 100644 --- a/src/window/window.cpp +++ b/src/window/window.cpp @@ -1,21 +1,33 @@ #include namespace Game::Window { - Window::Window() : mWindow(nullptr), mRunning(false), mRenderer() {} + Window::Window() : mWindow(nullptr), mRunning(false) { + } Window::~Window() { + // Stop render thread + if (mRenderThread.joinable()) { + mRenderThread.request_stop(); + mRenderThread.join(); + } + + mRenderer.destroy(); + if (mWindow) { SDL_DestroyWindow(mWindow); mWindow = nullptr; - mRunning = false; LOG("Window destroyed successfully"); - SDL_Quit(); } + SDL_Quit(); } bool Window::init(int width, int height, const std::string& title) { - if (SDL_Init(SDL_INIT_VIDEO) < 0) { return false; } - mWindow = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN); + if (!SDL_Init(SDL_INIT_VIDEO)) { + ERROR("Failed to initialize SDL: " << SDL_GetError()); + return false; + } + + mWindow = SDL_CreateWindow(title.c_str(), width, height, SDL_WINDOW_RESIZABLE); if (!mWindow) { ERROR("Failed to create window: " << SDL_GetError()); SDL_Quit(); @@ -24,12 +36,14 @@ namespace Game::Window { LOG("Window created successfully"); + // Spawn new thread for renderer if (!mRenderer.init(mWindow)) { SDL_DestroyWindow(mWindow); mWindow = nullptr; SDL_Quit(); return false; } + mRenderThread = std::jthread(std::bind_front(&Renderer::Renderer::run, &mRenderer)); mRunning = true; @@ -40,16 +54,12 @@ namespace Game::Window { SDL_Event event; while (mRunning) { while (SDL_PollEvent(&event)) { - if (event.type == SDL_QUIT) { + if (event.type == SDL_EVENT_QUIT) { mRunning = false; } // Handle other events (e.g., keyboard, mouse) here } - - mRenderer.clear(); - // Render game objects here - mRenderer.present(); } } } \ No newline at end of file