feat: Implement SDL2 Renderer as Concrete IRenderer Implementation

- Added IRenderer interface to define abstract rendering operations, decoupling application logic from specific rendering APIs.
- Implemented SDL2Renderer class, providing concrete methods for texture management, rendering primitives, and lifecycle management using SDL2.
- Introduced texture creation, updating, and destruction methods, along with rendering functions to clear the screen and present frames.
- Enhanced backend access for third-party library integration, ensuring flexibility in rendering operations.
This commit is contained in:
scawful
2025-10-07 14:19:48 -04:00
parent 6a439f7255
commit c6a581b202
3 changed files with 289 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
#pragma once
#include <SDL.h>
#include <memory>
#include <vector>
// 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

View File

@@ -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_Renderer, util::SDL_Deleter>(
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<TextureHandle>(
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_Surface, util::SDL_Surface_Deleter>(
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<SDL_Texture*>(texture), nullptr, converted_surface->pixels, converted_surface->pitch);
}
/**
* @brief Destroys an SDL_Texture.
*/
void SDL2Renderer::DestroyTexture(TextureHandle texture) {
if (texture) {
SDL_DestroyTexture(static_cast<SDL_Texture*>(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<SDL_Texture*>(texture), srcrect, dstrect);
}
/**
* @brief Sets the render target.
*/
void SDL2Renderer::SetRenderTarget(TextureHandle texture) {
SDL_SetRenderTarget(renderer_.get(), static_cast<SDL_Texture*>(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

View File

@@ -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<SDL_Renderer, util::SDL_Deleter> renderer_;
};
} // namespace gfx
} // namespace yaze