renderiranje - nazaj na singlethreaded ker me SDL ne mara :(

This commit is contained in:
2026-03-11 20:19:15 +01:00
parent d748ca63a0
commit 834f0b29c3
18 changed files with 230 additions and 75 deletions

View File

@@ -33,6 +33,9 @@ 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")
# Compile flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror -O2")
# Create the executable # Create the executable
add_executable(${PROJECT_NAME} ${SOURCES}) add_executable(${PROJECT_NAME} ${SOURCES})

View File

@@ -16,4 +16,8 @@ make -j
(Za Windows uporabite MSYS2 MINGW64 terminal) (Za Windows uporabite MSYS2 MINGW64 terminal)
## Licenca ## Licenca
Vsa koda (razen kadar je drugače navedeno ali uporabljeno) je licencirana pod "Lesser General Public License v2.1" edino (okrajšano na "LGPL v2.1-only"). Več informacij o licenci najdete v datoteki LICENSE. Vsa izvorna koda (razen kadar je drugače navedeno ali uporabljeno) je licencirana pod "Lesser General Public License v2.1" edino (okrajšano na "LGPL v2.1-only"). Več informacij o licenci najdete v datoteki LICENSE.
Vse slike (v direktorijo resources/) so podane pod "Creative Commons Attribution-ShareAlike" (CC BY-SA) licenco.
## Avtorske pravice
Vse avtorske pravice (copyright) so rezervirane k avtorju te izvorne kode/slik.

13
include/game/player.hpp Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include <object/entity.hpp>
namespace Game::AGame {
class Player : public Object::Entity {
using Object::Entity::Entity;
public:
void start() override;
void update() override;
};
}

View File

@@ -2,24 +2,47 @@
#include <string> #include <string>
#include <utils.hpp> #include <utils.hpp>
#include <object/transform.hpp>
#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>
#include <utility>
#include <memory>
namespace Game::Renderer { namespace Game::Renderer {
class Renderer;
class Texture; class Texture;
} }
namespace Game::Object { namespace Game::Object {
class Entity { class Entity {
public: public:
Entity(std::string& name, Game::Renderer::Texture* tex) : mName(name), mTex(tex) {} Entity(std::string name, std::shared_ptr<Game::Renderer::Texture> tex, Transform transform) : mName(name), mTex(tex), mTransform(transform), mIsActive(true) { LOG("Created Entity: " << mName); }
// I will define the copy and move constructors later - just deleted for now // I will define the copy and move constructors later - just deleted for now
DISABLE_COPY_AND_MOVE(Entity); Entity(const Entity&);
Entity& operator=(const Entity&);
Entity(Entity&&) noexcept;
Entity& operator=(Entity&&) noexcept;
~Entity(); ~Entity();
void start(); virtual void start() = 0;
void update(); virtual void update() = 0;
void render(Game::Renderer::Renderer* renderer);
// Setters and getters
void setTexture(std::shared_ptr<Game::Renderer::Texture> tex) { mTex = tex; }
void setName(const std::string& name) { mName = name; }
void setTransform(const Transform& transform) { mTransform = transform; }
void setActive(bool active) { mIsActive = active; }
std::shared_ptr<Game::Renderer::Texture> getTexture() { return mTex; }
std::string getName() { return mName; }
Transform* getTransform() { return &mTransform; }
bool isActive() { return mIsActive; }
protected: protected:
std::string mName; std::string mName;
Game::Renderer::Texture* mTex; std::shared_ptr<Game::Renderer::Texture> mTex;
Transform mTransform;
bool mIsActive;
private: private:
float mScaleConstant = 0.25f;
}; };
} }

View File

@@ -0,0 +1,11 @@
#pragma once
namespace Game::Object {
typedef struct {
float x, y;
float rotation; // In degrees, clockwise
float scaleX, scaleY;
} Transform;
constexpr Transform DEFAULT_TRANSFORM{0.f, 0.f, 0.f, 1.f, 1.f};
}

View File

@@ -13,7 +13,7 @@ namespace Game::Renderer {
~Renderer(); ~Renderer();
bool init(SDL_Window* window); bool init(SDL_Window* window);
void run(std::stop_token stoken); void renderFrame();
void destroy(); void destroy();
SDL_Renderer* getSDLRenderer() { return mRenderer; } SDL_Renderer* getSDLRenderer() { return mRenderer; }

View File

@@ -4,16 +4,18 @@
#include <SDL3_image/SDL_image.h> #include <SDL3_image/SDL_image.h>
#include <string> #include <string>
#include <utils.hpp> #include <utils.hpp>
#include <renderer/renderer.hpp>
namespace Game::Renderer { namespace Game::Renderer {
class Texture { class Texture {
public: public:
Texture(std::string& path, Renderer* renderer, std::string id = "noname"); Texture(const std::string& path, SDL_Renderer* renderer, std::string id = "noname");
Texture(const Texture&); Texture(const Texture&);
Texture& operator=(const Texture&); Texture& operator=(const Texture&);
DISABLE_MOVE(Texture); DISABLE_MOVE(Texture);
~Texture(); ~Texture();
SDL_Texture* getSDLTexture();
std::string getId();
private: private:
SDL_Texture* mTex; SDL_Texture* mTex;
std::string mId; std::string mId;

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include <shared_mutex>
#include <vector> #include <vector>
#include <memory>
#include <utils.hpp> #include <utils.hpp>
#include <object/entity.hpp> #include <object/entity.hpp>
@@ -10,15 +10,14 @@ namespace Game::State {
public: public:
static GameState& getInstance() { static GameState instance; return instance; } 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! // Retrieve a REFERENCE of the entities; DANGEROUS!
std::vector<Object::Entity>* getEntitiesRef(); std::vector<std::unique_ptr<Object::Entity>>* getEntitiesRef();
// Update entity at index, by REFERENCE // Update entity at index, by REFERENCE
Object::Entity* getAtIndex(size_t at); Object::Entity* getAtIndex(size_t at);
void addEntity(std::unique_ptr<Object::Entity> entity);
private: private:
mutable std::shared_mutex mMutex; std::vector<std::unique_ptr<Object::Entity>> mEntities;
std::vector<Object::Entity> mEntities;
}; };
} }

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <iostream>
#define DISABLE_COPY(Class) \ #define DISABLE_COPY(Class) \
Class(const Class&) = delete; \ Class(const Class&) = delete; \
Class& operator=(const Class&) = delete; Class& operator=(const Class&) = delete;
@@ -23,9 +25,7 @@
#define GAME_ENTITY(ClassName) \ #define GAME_ENTITY(ClassName) \
class ClassName : public Object::Entity { \ class ClassName : public Object::Entity { \
using Object::Entity::Entity; // Inherit constructors \ using Object::Entity::Entity;
#define END_GAME_ENTITY() \ #define END_GAME_ENTITY() \
}; };

View File

@@ -6,9 +6,7 @@
#include <utils.hpp> #include <utils.hpp>
#include <renderer/renderer.hpp> #include <renderer/renderer.hpp>
#include <thread> #include <state/gamestate.hpp>
#include <functional>
#include <chrono>
namespace Game::Window { namespace Game::Window {
class Window { class Window {
@@ -20,10 +18,11 @@ namespace Game::Window {
bool init(int width, int height, const std::string& title); bool init(int width, int height, const std::string& title);
void run(); void run();
Renderer::Renderer* getRenderer() { return &mRenderer; }
private: private:
SDL_Window* mWindow; SDL_Window* mWindow;
Renderer::Renderer mRenderer; Renderer::Renderer mRenderer;
std::jthread mRenderThread;
bool mRunning; bool mRunning;
}; };
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

14
src/game/player.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include <game/player.hpp>
namespace Game::AGame {
void Player::start() {
LOG("Created the Player");
}
void Player::update() {
if (!mIsActive) return;
//LOG("Updated Player");
mTransform.x += 0.5f; // Move right at a constant speed for testing
mTransform.rotation += 1.f; // Rotate clockwise for testing
}
}

View File

@@ -1,11 +1,20 @@
#include <iostream> #include <iostream>
#include <window/window.hpp> #include <window/window.hpp>
#include <state/gamestate.hpp>
#include <object/entity.hpp>
#include <object/transform.hpp>
#include <game/player.hpp>
#include <renderer/renderer.hpp>
#include <renderer/texture.hpp>
using namespace Game; using namespace Game;
int main(int argc, char* argv[]) { int main() {
Window::Window window = Window::Window(); Window::Window window = Window::Window();
window.init(1280, 720, "Game Window"); window.init(1280, 720, "Game Window");
State::GameState::getInstance().addEntity(std::make_unique<AGame::Player>("Player", std::make_shared<Game::Renderer::Texture>("../resources/missing_texture.png", window.getRenderer()->getSDLRenderer()), Object::DEFAULT_TRANSFORM));
window.run(); window.run();
return 0; return 0;

68
src/object/entity.cpp Normal file
View File

@@ -0,0 +1,68 @@
#include <object/entity.hpp>
#include <renderer/renderer.hpp>
#include <renderer/texture.hpp>
namespace Game::Object {
Entity::~Entity() {
LOG("Destroyed Entity: " << mName);
}
Entity::Entity(const Entity& other) : mName(other.mName), mTex(other.mTex), mTransform(other.mTransform), mIsActive(other.mIsActive) {
LOG("Copied Entity: " << mName);
}
Entity& Entity::operator=(const Entity& other) {
if (this != &other) {
mName = other.mName;
mTex = other.mTex;
mTransform = other.mTransform;
mIsActive = other.mIsActive;
}
return *this;
}
Entity::Entity(Entity&& other) noexcept : mName(std::move(other.mName)), mTex(other.mTex), mTransform(other.mTransform), mIsActive(other.mIsActive) {
other.mTex = nullptr;
LOG("Moved Entity: " << mName);
}
Entity& Entity::operator=(Entity&& other) noexcept {
if (this != &other) {
mName = std::move(other.mName);
mTex = other.mTex;
mTransform = other.mTransform;
mIsActive = other.mIsActive;
other.mTex = nullptr;
}
return *this;
}
void Entity::render(Game::Renderer::Renderer* renderer) {
if (!mIsActive || !mTex) return; // Don't render if not active or if there's no texture
float w, h;
SDL_GetTextureSize(mTex->getSDLTexture(), &w, &h);
SDL_FRect dst;
dst.w = w * mTransform.scaleX * mScaleConstant; // 1.f is HUGE, so this is just a constant to make the default scale more reasonable
dst.h = h * mTransform.scaleY * mScaleConstant;
// Top-left origin
dst.x = mTransform.x;
dst.y = mTransform.y;
SDL_FPoint center;
center.x = dst.w / 2.f;
center.y = dst.h / 2.f;
SDL_RenderTextureRotated(
renderer->getSDLRenderer(),
mTex->getSDLTexture(),
nullptr,
&dst,
mTransform.rotation,
&center,
SDL_FLIP_NONE
);
}
}

View File

@@ -1,5 +1,7 @@
#include <renderer/renderer.hpp> #include <renderer/renderer.hpp>
#include <utils.hpp> #include <utils.hpp>
#include <state/gamestate.hpp>
#include <object/entity.hpp>
namespace Game::Renderer { namespace Game::Renderer {
Renderer::Renderer() : mRenderer(nullptr) {} Renderer::Renderer() : mRenderer(nullptr) {}
@@ -34,12 +36,18 @@ namespace Game::Renderer {
return true; return true;
} }
void Renderer::run(std::stop_token stoken) { void Renderer::renderFrame() {
while (!stoken.stop_requested()) { mClear();
mClear();
// Get gamestate mutex and render the objects here; GameState::getState().objects or something, idk // Get gamestate and render the objects here; GameState::getState().objects or something, idk
mPresent(); auto entities = Game::State::GameState::getInstance().getEntitiesRef();
//LOG("Entity count: " << entities->size());
for (auto& entity : *entities) {
entity->render(this);
} }
mPresent();
} }
void Renderer::mClear() { void Renderer::mClear() {

View File

@@ -1,33 +1,38 @@
#include <renderer/texture.hpp> #include <renderer/texture.hpp>
namespace Game::Renderer { Game::Renderer::Texture::Texture(const std::string& path, SDL_Renderer* renderer, std::string id)
Texture::Texture(std::string& path, Renderer* renderer, std::string id) : mTex(nullptr), mId(id) {
: mTex(nullptr), mId(id) { SDL_Surface* surf = IMG_Load(path.c_str());
SDL_Surface* surf = IMG_Load(path.c_str()); if (!surf) {
if (!surf) { ERROR("Failed to load image at " << path);
ERROR("Failed to load image at " << path); return;
return;
}
mTex = SDL_CreateTextureFromSurface(renderer->getSDLRenderer(), surf);
SDL_DestroySurface(surf);
} }
Texture::Texture(const Texture& other) { mTex = SDL_CreateTextureFromSurface(renderer, surf);
// Copy the references, since copying memory would require re-initing a bunch of things - for now SDL_DestroySurface(surf);
this->mTex = other.mTex; }
}
Game::Renderer::Texture::Texture(const Texture& other) {
Texture& Texture::operator=(const Texture& other) { // Copy the references, since copying memory would require re-initing a bunch of things - for now
// Same reasoning this->mTex = other.mTex;
this->mTex = other.mTex; }
return *this;
} Game::Renderer::Texture& Game::Renderer::Texture::operator=(const Texture& other) {
// Same reasoning
Texture::~Texture() { this->mTex = other.mTex;
if (mTex) return *this;
SDL_DestroyTexture(mTex); }
LOG("Destroyed texture '" << mId << "'")
} Game::Renderer::Texture::~Texture() {
if (mTex)
SDL_DestroyTexture(mTex);
LOG("Destroyed texture '" << mId << "'")
}
SDL_Texture* Game::Renderer::Texture::getSDLTexture() {
return mTex;
}
std::string Game::Renderer::Texture::getId() {
return mId;
} }

View File

@@ -2,24 +2,22 @@
#include <iostream> #include <iostream>
namespace Game::State { namespace Game::State {
// TODO: Caller should also hold these locks std::vector<std::unique_ptr<Object::Entity>>* GameState::getEntitiesRef() {
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; return &mEntities;
} }
Object::Entity* GameState::getAtIndex(size_t at) { Object::Entity* GameState::getAtIndex(size_t at) {
std::shared_lock lock(mMutex);
try { try {
return &mEntities.at(at); return mEntities.at(at).get();
} catch (const std::out_of_range& e) { } catch (const std::out_of_range& e) {
WARN("Tried to access entity from GameState out of range!"); WARN("Tried to access entity from GameState out of range!");
return nullptr; return nullptr;
} }
} }
void GameState::addEntity(std::unique_ptr<Object::Entity> entity) {
mEntities.push_back(std::move(entity));
GameState::getInstance().getAtIndex(mEntities.size() - 1)->start(); // Call start() on the newly added entity
LOG("Added entity '" << GameState::getInstance().getAtIndex(mEntities.size() - 1)->getName() << "' to GameState");
}
} }

View File

@@ -1,16 +1,9 @@
#include <window/window.hpp> #include <window/window.hpp>
namespace Game::Window { namespace Game::Window {
Window::Window() : mWindow(nullptr), mRunning(false) { Window::Window() : mWindow(nullptr), mRenderer(), mRunning(false) { }
}
Window::~Window() { Window::~Window() {
// Stop render thread
if (mRenderThread.joinable()) {
mRenderThread.request_stop();
mRenderThread.join();
}
mRenderer.destroy(); mRenderer.destroy();
if (mWindow) { if (mWindow) {
@@ -36,14 +29,12 @@ 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;
@@ -60,6 +51,14 @@ namespace Game::Window {
// Handle other events (e.g., keyboard, mouse) here // Handle other events (e.g., keyboard, mouse) here
} }
auto entities = State::GameState::getInstance().getEntitiesRef();
for (auto& entity : *entities) {
entity->update();
}
mRenderer.renderFrame();
SDL_Delay(16); // ~60 FPS target, maybe make dynamic based on avg. frame time - TODO
} }
} }
} }