diff --git a/src/app/gfx/backend/irenderer.h b/src/app/gfx/backend/irenderer.h new file mode 100644 index 00000000..26bb9dc2 --- /dev/null +++ b/src/app/gfx/backend/irenderer.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include + +// Forward declarations prevent circular dependencies and speed up compilation. +// Instead of including the full header, we just tell the compiler that these types exist. +namespace yaze { +namespace gfx { +class Bitmap; +} +} + +namespace yaze { +namespace gfx { + +/** + * @brief An abstract handle representing a texture. + * + * This typedef allows the underlying texture implementation (e.g., SDL_Texture*, + * an OpenGL texture ID, etc.) to be hidden from the application logic. + */ +using TextureHandle = void*; + +/** + * @interface IRenderer + * @brief Defines an abstract interface for all rendering operations. + * + * This interface decouples the application from any specific rendering API (like SDL2, SDL3, OpenGL, etc.). + * It provides a contract for creating textures, managing their lifecycle, and performing + * primitive drawing operations. The goal is to program against this interface, allowing the + * concrete rendering backend to be swapped out with minimal changes to the application code. + */ +class IRenderer { +public: + virtual ~IRenderer() = default; + + // --- Initialization and Lifecycle --- + + /** + * @brief Initializes the renderer with a given window. + * @param window A pointer to the SDL_Window to render into. + * @return True if initialization was successful, false otherwise. + */ + virtual bool Initialize(SDL_Window* window) = 0; + + /** + * @brief Shuts down the renderer and releases all associated resources. + */ + virtual void Shutdown() = 0; + + // --- Texture Management --- + + /** + * @brief Creates a new, empty texture. + * @param width The width of the texture in pixels. + * @param height The height of the texture in pixels. + * @return An abstract TextureHandle to the newly created texture, or nullptr on failure. + */ + virtual TextureHandle CreateTexture(int width, int height) = 0; + + /** + * @brief Updates a texture with the pixel data from a Bitmap. + * @param texture The handle of the texture to update. + * @param bitmap The Bitmap containing the new pixel data. + */ + virtual void UpdateTexture(TextureHandle texture, const Bitmap& bitmap) = 0; + + /** + * @brief Destroys a texture and frees its associated resources. + * @param texture The handle of the texture to destroy. + */ + virtual void DestroyTexture(TextureHandle texture) = 0; + + // --- Rendering Primitives --- + + /** + * @brief Clears the entire render target with the current draw color. + */ + virtual void Clear() = 0; + + /** + * @brief Presents the back buffer to the screen, making the rendered content visible. + */ + virtual void Present() = 0; + + /** + * @brief Copies a portion of a texture to the current render target. + * @param texture The source texture handle. + * @param srcrect A pointer to the source rectangle, or nullptr for the entire texture. + * @param dstrect A pointer to the destination rectangle, or nullptr for the entire render target. + */ + virtual void RenderCopy(TextureHandle texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect) = 0; + + /** + * @brief Sets the render target for subsequent drawing operations. + * @param texture The texture to set as the render target, or nullptr to set it back to the default (the window). + */ + virtual void SetRenderTarget(TextureHandle texture) = 0; + + /** + * @brief Sets the color used for drawing operations (e.g., Clear). + * @param color The SDL_Color to use. + */ + virtual void SetDrawColor(SDL_Color color) = 0; + + // --- Backend-specific Access --- + + /** + * @brief Provides an escape hatch to get the underlying, concrete renderer object. + * + * This is necessary for integrating with third-party libraries like ImGui that are tied + * to a specific rendering backend (e.g., SDL_Renderer*, ID3D11Device*). + * + * @return A void pointer to the backend-specific renderer object. The caller is responsible + * for casting it to the correct type. + */ + virtual void* GetBackendRenderer() = 0; +}; + +} // namespace gfx +} // namespace yaze diff --git a/src/app/gfx/backend/sdl2_renderer.cc b/src/app/gfx/backend/sdl2_renderer.cc new file mode 100644 index 00000000..b651bf5c --- /dev/null +++ b/src/app/gfx/backend/sdl2_renderer.cc @@ -0,0 +1,115 @@ +#include "app/gfx/backend/sdl2_renderer.h" +#include "absl/strings/str_format.h" + +namespace yaze { +namespace gfx { + +SDL2Renderer::SDL2Renderer() = default; + +SDL2Renderer::~SDL2Renderer() { + Shutdown(); +} + +/** + * @brief Initializes the SDL2 renderer. + * This function creates an accelerated SDL2 renderer and attaches it to the given window. + */ +bool SDL2Renderer::Initialize(SDL_Window* window) { + // Create an SDL2 renderer with hardware acceleration. + renderer_ = std::unique_ptr( + SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED)); + + if (renderer_ == nullptr) { + // Log an error if renderer creation fails. + printf("SDL_CreateRenderer Error: %s\n", SDL_GetError()); + return false; + } + + // Set the blend mode to allow for transparency. + SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND); + return true; +} + +/** + * @brief Shuts down the renderer. + * The underlying SDL_Renderer is managed by a unique_ptr, so its destruction is handled automatically. + */ +void SDL2Renderer::Shutdown() { + renderer_.reset(); +} + +/** + * @brief Creates an SDL_Texture. + * The texture is created with streaming access, which is suitable for textures that are updated frequently. + */ +TextureHandle SDL2Renderer::CreateTexture(int width, int height) { + // The TextureHandle is a void*, so we cast the SDL_Texture* to it. + return static_cast( + SDL_CreateTexture(renderer_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, width, height) + ); +} + +/** + * @brief Updates an SDL_Texture with data from a Bitmap. + * This involves converting the bitmap's surface to the correct format and updating the texture. + */ +void SDL2Renderer::UpdateTexture(TextureHandle texture, const Bitmap& bitmap) { + SDL_Surface* surface = bitmap.surface(); + if (!texture || !surface) return; + + // Convert the bitmap's surface to RGBA8888 format for compatibility with the texture. + auto converted_surface = std::unique_ptr( + SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA8888, 0)); + + if (!converted_surface) return; + + // Update the texture with the pixels from the converted surface. + SDL_UpdateTexture(static_cast(texture), nullptr, converted_surface->pixels, converted_surface->pitch); +} + +/** + * @brief Destroys an SDL_Texture. + */ +void SDL2Renderer::DestroyTexture(TextureHandle texture) { + if (texture) { + SDL_DestroyTexture(static_cast(texture)); + } +} + +/** + * @brief Clears the screen with the current draw color. + */ +void SDL2Renderer::Clear() { + SDL_RenderClear(renderer_.get()); +} + +/** + * @brief Presents the rendered frame to the screen. + */ +void SDL2Renderer::Present() { + SDL_RenderPresent(renderer_.get()); +} + +/** + * @brief Copies a texture to the render target. + */ +void SDL2Renderer::RenderCopy(TextureHandle texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect) { + SDL_RenderCopy(renderer_.get(), static_cast(texture), srcrect, dstrect); +} + +/** + * @brief Sets the render target. + */ +void SDL2Renderer::SetRenderTarget(TextureHandle texture) { + SDL_SetRenderTarget(renderer_.get(), static_cast(texture)); +} + +/** + * @brief Sets the draw color. + */ +void SDL2Renderer::SetDrawColor(SDL_Color color) { + SDL_SetRenderDrawColor(renderer_.get(), color.r, color.g, color.b, color.a); +} + +} // namespace gfx +} // namespace yaze diff --git a/src/app/gfx/backend/sdl2_renderer.h b/src/app/gfx/backend/sdl2_renderer.h new file mode 100644 index 00000000..e84b7abe --- /dev/null +++ b/src/app/gfx/backend/sdl2_renderer.h @@ -0,0 +1,51 @@ +#pragma once + +#include "app/gfx/backend/irenderer.h" +#include "util/sdl_deleter.h" + +namespace yaze { +namespace gfx { + +/** + * @class SDL2Renderer + * @brief A concrete implementation of the IRenderer interface using SDL2. + * + * This class encapsulates all rendering logic that is specific to the SDL2_render API. + * It translates the abstract calls from the IRenderer interface into concrete SDL2 commands. + * This is the first step in abstracting the renderer, allowing the rest of the application + * to be independent of SDL2. + */ +class SDL2Renderer : public IRenderer { +public: + SDL2Renderer(); + ~SDL2Renderer() override; + + // --- Lifecycle and Initialization --- + bool Initialize(SDL_Window* window) override; + void Shutdown() override; + + // --- Texture Management --- + TextureHandle CreateTexture(int width, int height) override; + void UpdateTexture(TextureHandle texture, const Bitmap& bitmap) override; + void DestroyTexture(TextureHandle texture) override; + + // --- Rendering Primitives --- + void Clear() override; + void Present() override; + void RenderCopy(TextureHandle texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect) override; + void SetRenderTarget(TextureHandle texture) override; + void SetDrawColor(SDL_Color color) override; + + /** + * @brief Provides access to the underlying SDL_Renderer*. + * @return A void pointer that can be safely cast to an SDL_Renderer*. + */ + void* GetBackendRenderer() override { return renderer_.get(); } + +private: + // The core SDL2 renderer object, managed by a unique_ptr with a custom deleter. + std::unique_ptr renderer_; +}; + +} // namespace gfx +} // namespace yaze