epic: refactor SDL2_Renderer usage to IRenderer and queued texture rendering

- Updated the testing guide to clarify the testing framework's organization and execution methods, improving user understanding.
- Refactored CMakeLists to include new platform-specific files, ensuring proper integration of the rendering backend.
- Modified main application files to utilize the new IRenderer interface, enhancing flexibility in rendering operations.
- Implemented deferred texture management in various components, allowing for more efficient graphics handling and improved performance.
- Introduced new methods for texture creation and updates, streamlining the rendering process across the application.
- Enhanced logging and error handling in the rendering pipeline to facilitate better debugging and diagnostics.
This commit is contained in:
scawful
2025-10-07 17:15:11 -04:00
parent 9e6f538520
commit 6c331f1fd0
101 changed files with 1401 additions and 2677 deletions

View File

@@ -3,40 +3,36 @@
#include <cmath>
#include <string>
#include "app/gfx/bpp_format_manager.h"
#include "app/core/window.h"
#include "app/gfx/atlas_renderer.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/performance_profiler.h"
#include "app/gui/canvas_utils.h"
#include "app/gui/color.h"
#include "app/gui/style.h"
#include "app/gui/canvas/canvas_automation_api.h"
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
#include "util/log.h"
namespace yaze::gui {
using core::Renderer;
// Define constructors and destructor in .cc to avoid incomplete type issues with unique_ptr
Canvas::Canvas() { InitializeDefaults(); }
// Default constructor
Canvas::Canvas() : renderer_(nullptr) { InitializeDefaults(); }
// Legacy constructors (renderer is optional for backward compatibility)
Canvas::Canvas(const std::string& id)
: canvas_id_(id), context_id_(id + "Context") {
: renderer_(nullptr), canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
}
Canvas::Canvas(const std::string& id, ImVec2 canvas_size)
: canvas_id_(id), context_id_(id + "Context") {
: renderer_(nullptr), canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
}
Canvas::Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size)
: canvas_id_(id), context_id_(id + "Context") {
: renderer_(nullptr), canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
@@ -44,7 +40,39 @@ Canvas::Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_si
}
Canvas::Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale)
: canvas_id_(id), context_id_(id + "Context") {
: renderer_(nullptr), canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
config_.global_scale = global_scale;
SetGridSize(grid_size);
}
// New constructors with renderer support (for migration to IRenderer pattern)
Canvas::Canvas(gfx::IRenderer* renderer) : renderer_(renderer) { InitializeDefaults(); }
Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id)
: renderer_(renderer), canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
}
Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size)
: renderer_(renderer), canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
}
Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size)
: renderer_(renderer), canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
SetGridSize(grid_size);
}
Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale)
: renderer_(renderer), canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
@@ -243,8 +271,7 @@ void Canvas::ShowColorAnalysis() {
bool Canvas::ApplyROMPalette(int group_index, int palette_index) {
if (palette_editor_ && bitmap_) {
return palette_editor_->ApplyROMPalette(bitmap_, group_index,
palette_index);
return palette_editor_->ApplyROMPalette(bitmap_, group_index, palette_index);
}
return false;
}
@@ -330,7 +357,7 @@ void Canvas::End() {
// ==================== Legacy Interface ====================
void Canvas::UpdateColorPainter(gfx::Bitmap& bitmap, const ImVec4& color,
void Canvas::UpdateColorPainter(gfx::IRenderer* renderer, gfx::Bitmap& bitmap, const ImVec4& color,
const std::function<void()>& event,
int tile_size, float scale) {
config_.global_scale = scale;
@@ -340,6 +367,7 @@ void Canvas::UpdateColorPainter(gfx::Bitmap& bitmap, const ImVec4& color,
DrawBitmap(bitmap, 2, scale);
if (DrawSolidTilePainter(color, tile_size)) {
event();
bitmap.UpdateTexture();
}
DrawGrid();
DrawOverlay();
@@ -1796,7 +1824,7 @@ bool Canvas::ConvertBitmapFormat(gfx::BppFormat target_format) {
bitmap_->set_data(converted_data);
// Update the renderer
core::Renderer::Get().UpdateBitmap(bitmap_);
bitmap_->UpdateTexture();
return true;
} catch (const std::exception& e) {

View File

@@ -53,13 +53,26 @@ enum class CanvasGridSize { k8x8, k16x16, k32x32, k64x64 };
*/
class Canvas {
public:
// Default constructor
Canvas();
~Canvas();
// Legacy constructors (renderer is optional for backward compatibility)
explicit Canvas(const std::string& id);
explicit Canvas(const std::string& id, ImVec2 canvas_size);
explicit Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size);
explicit Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale);
// New constructors with renderer support (for migration to IRenderer pattern)
explicit Canvas(gfx::IRenderer* renderer);
explicit Canvas(gfx::IRenderer* renderer, const std::string& id);
explicit Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size);
explicit Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size);
explicit Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale);
// Set renderer after construction (for late initialization)
void SetRenderer(gfx::IRenderer* renderer) { renderer_ = renderer; }
gfx::IRenderer* renderer() const { return renderer_; }
void SetGridSize(CanvasGridSize grid_size) {
switch (grid_size) {
@@ -89,7 +102,7 @@ class Canvas {
return CanvasGridSize::k16x16; // Default
}
void UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
void UpdateColorPainter(gfx::IRenderer* renderer, gfx::Bitmap &bitmap, const ImVec4 &color,
const std::function<void()> &event, int tile_size,
float scale = 1.0f);
@@ -248,20 +261,6 @@ class Canvas {
bool WasDoubleClicked(ImGuiMouseButton button = ImGuiMouseButton_Left) const;
ImVec2 GetLastClickPosition() const;
private:
void DrawContextMenuItem(const ContextMenuItem& item);
// Tile painter shows a preview of the currently selected tile
// and allows the user to left click to paint the tile or right
// click to select a new tile to paint with.
// (Moved to public section)
// Draws a tile on the canvas at the specified position
// (Moved to public section)
// These methods are now public - see public section above
public:
// Tile painter methods
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale = 1.0f);
bool DrawSolidTilePainter(const ImVec4 &color, int size);
@@ -401,7 +400,10 @@ class Canvas {
Rom *rom() const { return rom_; }
private:
void DrawContextMenuItem(const ContextMenuItem& item);
// Modular configuration and state
gfx::IRenderer* renderer_ = nullptr;
CanvasConfig config_;
CanvasSelection selection_;
std::unique_ptr<PaletteWidget> palette_editor_;
@@ -506,13 +508,21 @@ void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap,
class ScopedCanvas {
public:
/**
* @brief Construct and begin a new canvas
* @brief Construct and begin a new canvas (legacy constructor without renderer)
*/
explicit ScopedCanvas(const std::string& id, ImVec2 canvas_size = ImVec2(0, 0))
: canvas_(new Canvas(id, canvas_size)), owned_(true), active_(true) {
canvas_->Begin();
}
/**
* @brief Construct and begin a new canvas (with optional renderer)
*/
explicit ScopedCanvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size = ImVec2(0, 0))
: canvas_(new Canvas(renderer, id, canvas_size)), owned_(true), active_(true) {
canvas_->Begin();
}
/**
* @brief Wrap existing canvas with RAII
*/

View File

@@ -1,14 +1,9 @@
#include "canvas_context_menu.h"
#include <algorithm>
#include <sstream>
#include <iomanip>
#include "app/core/window.h"
#include "app/gfx/arena.h"
#include "app/gfx/performance_profiler.h"
#include "app/gfx/performance_dashboard.h"
#include "app/gui/widgets/palette_widget.h"
#include "app/gui/bpp_format_ui.h"
#include "app/gui/icons.h"
#include "app/gui/color.h"
#include "app/gui/canvas/canvas_modals.h"
@@ -303,15 +298,21 @@ void CanvasContextMenu::RenderBitmapOperationsMenu(gfx::Bitmap* bitmap) {
if (ImGui::BeginMenu("Format")) {
if (ImGui::MenuItem("Indexed")) {
bitmap->Reformat(gfx::BitmapFormat::kIndexed);
core::Renderer::Get().UpdateBitmap(bitmap);
// Queue texture update via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, bitmap);
}
if (ImGui::MenuItem("4BPP")) {
bitmap->Reformat(gfx::BitmapFormat::k4bpp);
core::Renderer::Get().UpdateBitmap(bitmap);
// Queue texture update via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, bitmap);
}
if (ImGui::MenuItem("8BPP")) {
bitmap->Reformat(gfx::BitmapFormat::k8bpp);
core::Renderer::Get().UpdateBitmap(bitmap);
// Queue texture update via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, bitmap);
}
ImGui::EndMenu();
}

View File

@@ -1,7 +1,7 @@
#include "canvas_utils.h"
#include <cmath>
#include "app/core/window.h"
#include "app/gfx/arena.h"
#include "app/gfx/snes_palette.h"
#include "util/log.h"
@@ -9,8 +9,6 @@ namespace yaze {
namespace gui {
namespace CanvasUtils {
using core::Renderer;
ImVec2 AlignToGrid(ImVec2 pos, float grid_step) {
return ImVec2(std::floor(pos.x / grid_step) * grid_step,
std::floor(pos.y / grid_step) * grid_step);
@@ -95,35 +93,30 @@ bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager) {
}
}
bool ApplyPaletteGroup(gfx::Bitmap* bitmap,
const CanvasPaletteManager& palette_manager,
bool ApplyPaletteGroup(gfx::IRenderer* renderer, gfx::Bitmap* bitmap, const CanvasPaletteManager& palette_manager,
int group_index, int palette_index) {
if (!bitmap || group_index < 0 ||
group_index >=
static_cast<int>(palette_manager.rom_palette_groups.size())) {
if (!bitmap) return false;
if (group_index < 0 || group_index >= palette_manager.rom_palette_groups.size()) {
return false;
}
try {
const auto& selected_palette =
palette_manager.rom_palette_groups[group_index];
// Apply the palette based on the index
if (palette_index >= 0 && palette_index < 8) {
bitmap->SetPaletteWithTransparent(selected_palette, palette_index);
} else {
bitmap->SetPalette(selected_palette);
}
Renderer::Get().UpdateBitmap(bitmap);
LOG_DEBUG("Canvas", "Applied palette group %d, index %d to bitmap",
group_index, palette_index);
return true;
} catch (const std::exception& e) {
LOG_ERROR("Canvas", "Failed to apply palette");
return false;
const auto& palette = palette_manager.rom_palette_groups[group_index];
// Apply the full palette or use SetPaletteWithTransparent if palette_index is specified
if (palette_index == 0) {
bitmap->SetPalette(palette);
} else {
bitmap->SetPaletteWithTransparent(palette, palette_index);
}
bitmap->set_modified(true);
// Queue texture update via Arena's deferred system
if (renderer) {
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, bitmap);
}
return true;
}
// Drawing utility functions

View File

@@ -89,7 +89,7 @@ int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale, int ti
// Palette management utilities
bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager);
bool ApplyPaletteGroup(gfx::Bitmap* bitmap, const CanvasPaletteManager& palette_manager,
bool ApplyPaletteGroup(gfx::IRenderer* renderer, gfx::Bitmap* bitmap, const CanvasPaletteManager& palette_manager,
int group_index, int palette_index);
// Drawing utility functions (moved from Canvas class)

View File

@@ -28,6 +28,12 @@ static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter(
? ImGuiInputTextFlags_CharsHexadecimal
: ImGuiInputTextFlags_CharsDecimal;
}
// Helper: returns true if label is "invisible" (starts with "##")
static inline bool IsInvisibleLabel(const char* label) {
return label && label[0] == '#' && label[1] == '#';
}
bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data,
const void* p_step, const void* p_step_fast,
const char* format, float input_width,
@@ -52,22 +58,24 @@ bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data,
flags |= ImGuiInputTextFlags_AutoSelectAll;
bool value_changed = false;
// if (p_step == NULL) {
// ImGui::SetNextItemWidth(input_width);
// if (InputText("", buf, IM_ARRAYSIZE(buf), flags))
// value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
// } else {
const float button_size = GetFrameHeight();
AlignTextToFramePadding();
Text("%s", label);
SameLine();
// Support invisible labels (##) by not rendering the label, but still using it for ID
bool invisible_label = IsInvisibleLabel(label);
if (!invisible_label) {
AlignTextToFramePadding();
Text("%s", label);
SameLine();
}
BeginGroup(); // The only purpose of the group here is to allow the caller
// to query item data e.g. IsItemActive()
PushID(label);
SetNextItemWidth(ImMax(
1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
// Place the label on the left of the input field
// Place the label on the left of the input field, unless invisible
PushStyleVar(ImGuiStyleVar_ItemSpacing,
ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
PushStyleVar(ImGuiStyleVar_FramePadding,

View File

@@ -5,7 +5,7 @@
#include "util/file_util.h"
#include "app/gui/theme_manager.h"
#include "app/editor/ui/background_renderer.h"
#include "app/core/platform/font_loader.h"
#include "app/platform/font_loader.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "imgui/imgui.h"
@@ -1293,7 +1293,7 @@ void DrawFontManager() {
static bool font_selected = false;
ImGui::Text("Loaded fonts");
for (const auto &loaded_font : core::font_registry.fonts) {
for (const auto &loaded_font : font_registry.fonts) {
ImGui::Text("%s", loaded_font.font_path);
}
ImGui::Separator();

View File

@@ -14,7 +14,7 @@
#endif
namespace yaze {
namespace app {
namespace gui {
AgentChatWidget::AgentChatWidget()
@@ -334,5 +334,5 @@ void AgentChatWidget::ScrollToBottom() {
}
} // namespace gui
} // namespace app
} // namespace yaze

View File

@@ -10,7 +10,7 @@
#include "app/rom.h"
namespace yaze {
namespace app {
namespace gui {
/**
@@ -77,7 +77,7 @@ class AgentChatWidget {
};
} // namespace gui
} // namespace app
} // namespace yaze
#endif // YAZE_APP_GUI_WIDGETS_AGENT_CHAT_WIDGET_H_

View File

@@ -8,7 +8,7 @@
#include "imgui/imgui.h"
namespace yaze {
namespace app {
namespace gui {
CollaborationPanel::CollaborationPanel()
@@ -660,5 +660,5 @@ void CollaborationPanel::RenderApprovalProposal(
}
} // namespace gui
} // namespace app
} // namespace yaze

View File

@@ -15,7 +15,7 @@
#endif
namespace yaze {
namespace app {
namespace gui {
/**
@@ -180,7 +180,7 @@ class CollaborationPanel {
};
} // namespace gui
} // namespace app
} // namespace yaze
#endif // YAZE_APP_GUI_WIDGETS_COLLABORATION_PANEL_H_

View File

@@ -1,4 +1,5 @@
#include "app/gui/widgets/dungeon_object_emulator_preview.h"
#include "app/gfx/backend/irenderer.h"
#include "app/zelda3/dungeon/room.h"
#include "app/zelda3/dungeon/room_object.h"
@@ -11,23 +12,22 @@ namespace gui {
DungeonObjectEmulatorPreview::DungeonObjectEmulatorPreview() {
snes_instance_ = std::make_unique<emu::Snes>();
object_texture_ = SDL_CreateTexture(core::Renderer::Get().renderer(),
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, 256, 256);
}
DungeonObjectEmulatorPreview::~DungeonObjectEmulatorPreview() {
if (object_texture_) {
SDL_DestroyTexture(object_texture_);
}
// if (object_texture_) {
// renderer_->DestroyTexture(object_texture_);
// }
}
void DungeonObjectEmulatorPreview::Initialize(Rom* rom) {
rom_ = rom;
if (rom_ && rom_->is_loaded()) {
auto rom_data = rom_->vector();
snes_instance_->Init(rom_data);
}
void DungeonObjectEmulatorPreview::Initialize(gfx::IRenderer* renderer, Rom* rom) {
renderer_ = renderer;
rom_ = rom;
snes_instance_ = std::make_unique<emu::Snes>();
std::vector<uint8_t> rom_data = rom->vector();
snes_instance_->Init(rom_data);
// object_texture_ = renderer_->CreateTexture(256, 256);
}
void DungeonObjectEmulatorPreview::Render() {
@@ -286,9 +286,9 @@ void DungeonObjectEmulatorPreview::TriggerEmulatedRender() {
// 15. Get the rendered pixels from PPU
void* pixels = nullptr;
int pitch = 0;
if (SDL_LockTexture(object_texture_, nullptr, &pixels, &pitch) == 0) {
if (renderer_->LockTexture(object_texture_, nullptr, &pixels, &pitch)) {
snes_instance_->SetPixels(static_cast<uint8_t*>(pixels));
SDL_UnlockTexture(object_texture_);
renderer_->UnlockTexture(object_texture_);
}
}

View File

@@ -4,6 +4,12 @@
#include "app/emu/snes.h"
#include "app/rom.h"
namespace yaze {
namespace gfx {
class IRenderer;
} // namespace gfx
}
namespace yaze {
namespace gui {
@@ -12,16 +18,17 @@ class DungeonObjectEmulatorPreview {
DungeonObjectEmulatorPreview();
~DungeonObjectEmulatorPreview();
void Initialize(Rom* rom);
void Initialize(gfx::IRenderer* renderer, Rom* rom);
void Render();
private:
void RenderControls();
void TriggerEmulatedRender();
gfx::IRenderer* renderer_ = nullptr;
Rom* rom_ = nullptr;
std::unique_ptr<emu::Snes> snes_instance_;
SDL_Texture* object_texture_ = nullptr;
void* object_texture_ = nullptr;
int object_id_ = 0;
int room_id_ = 0;

View File

@@ -2,15 +2,13 @@
#include <algorithm>
#include <map>
#include "app/core/window.h"
#include "app/gfx/arena.h"
#include "app/gui/color.h"
#include "util/log.h"
namespace yaze {
namespace gui {
using core::Renderer;
void PaletteWidget::Initialize(Rom* rom) {
rom_ = rom;
rom_palettes_loaded_ = false;
@@ -169,7 +167,9 @@ bool PaletteWidget::ApplyROMPalette(gfx::Bitmap* bitmap, int group_index, int pa
bitmap->SetPalette(selected_palette);
}
Renderer::Get().UpdateBitmap(bitmap);
// Queue texture update via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, bitmap);
current_group_index_ = group_index;
current_palette_index_ = palette_index;