feat: Implement lazy initialization for Emulator and Renderer
- Updated Emulator to support optional lazy initialization of the renderer, enhancing flexibility during runtime. - Introduced a new method in IRenderer for creating textures with specific pixel formats, improving texture management for the emulator. - Refactored texture command processing in Arena to handle empty queues more gracefully. - Enhanced SDL2Renderer to support the new texture creation method, ensuring compatibility with emulator requirements.
This commit is contained in:
@@ -233,6 +233,9 @@ constexpr const char* kMusicEditorName = ICON_MD_MUSIC_NOTE " Music Editor";
|
|||||||
void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& filename) {
|
void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& filename) {
|
||||||
renderer_ = renderer;
|
renderer_ = renderer;
|
||||||
|
|
||||||
|
// NOTE: Emulator will be initialized later when a ROM is loaded
|
||||||
|
// We just store the renderer for now
|
||||||
|
|
||||||
// Point to a blank editor set when no ROM is loaded
|
// Point to a blank editor set when no ROM is loaded
|
||||||
current_editor_set_ = &blank_editor_set_;
|
current_editor_set_ = &blank_editor_set_;
|
||||||
|
|
||||||
@@ -2097,6 +2100,11 @@ absl::Status EditorManager::LoadAssets() {
|
|||||||
|
|
||||||
auto start_time = std::chrono::steady_clock::now();
|
auto start_time = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
// Set renderer for emulator (lazy initialization happens in Run())
|
||||||
|
if (renderer_) {
|
||||||
|
emulator_.set_renderer(renderer_);
|
||||||
|
}
|
||||||
|
|
||||||
current_editor_set_->overworld_editor_.Initialize();
|
current_editor_set_->overworld_editor_.Initialize();
|
||||||
current_editor_set_->message_editor_.Initialize();
|
current_editor_set_->message_editor_.Initialize();
|
||||||
// Initialize the dungeon editor with the renderer
|
// Initialize the dungeon editor with the renderer
|
||||||
|
|||||||
@@ -50,31 +50,50 @@ using ImGui::TableNextColumn;
|
|||||||
using ImGui::Text;
|
using ImGui::Text;
|
||||||
|
|
||||||
void Emulator::Initialize(gfx::IRenderer* renderer, const std::vector<uint8_t>& rom_data) {
|
void Emulator::Initialize(gfx::IRenderer* renderer, const std::vector<uint8_t>& rom_data) {
|
||||||
|
// This method is now optional - emulator can be initialized lazily in Run()
|
||||||
renderer_ = renderer;
|
renderer_ = renderer;
|
||||||
rom_data_ = rom_data;
|
rom_data_ = rom_data;
|
||||||
snes_.Init(rom_data_);
|
|
||||||
|
// Reset state for new ROM
|
||||||
|
running_ = false;
|
||||||
|
snes_initialized_ = false;
|
||||||
|
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Emulator::Run(Rom* rom) {
|
void Emulator::Run(Rom* rom) {
|
||||||
static bool loaded = false;
|
// Lazy initialization: set renderer from Controller if not set yet
|
||||||
if (!snes_.running() && rom->is_loaded()) {
|
if (!renderer_) {
|
||||||
ppu_texture_ = renderer_->CreateTexture(512, 480);
|
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
|
||||||
if (ppu_texture_ == NULL) {
|
"Emulator renderer not initialized");
|
||||||
printf("Failed to create texture: %s\n", SDL_GetError());
|
return;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
// Initialize SNES and create PPU texture on first run
|
||||||
|
// This happens lazily when user opens the emulator window
|
||||||
|
if (!snes_initialized_ && rom->is_loaded()) {
|
||||||
|
// Create PPU texture with correct format for SNES emulator
|
||||||
|
// ARGB8888 matches the XBGR format used by the SNES PPU (pixel format 1)
|
||||||
|
if (!ppu_texture_) {
|
||||||
|
ppu_texture_ = renderer_->CreateTextureWithFormat(
|
||||||
|
512, 480, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING);
|
||||||
|
if (ppu_texture_ == NULL) {
|
||||||
|
printf("Failed to create PPU texture: %s\n", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimize texture for better performance
|
// Initialize SNES with ROM data (either from Initialize() or from rom parameter)
|
||||||
// renderer_->SetTextureBlendMode(ppu_texture_, SDL_BLENDMODE_NONE);
|
if (rom_data_.empty()) {
|
||||||
rom_data_ = rom->vector();
|
rom_data_ = rom->vector();
|
||||||
|
}
|
||||||
snes_.Init(rom_data_);
|
snes_.Init(rom_data_);
|
||||||
|
|
||||||
// Note: PPU pixel format set to 1 (XBGR) in Init() which matches ARGB8888 texture
|
// Note: PPU pixel format set to 1 (XBGR) in Init() which matches ARGB8888 texture
|
||||||
|
|
||||||
wanted_frames_ = 1.0 / (snes_.memory().pal_timing() ? 50.0 : 60.0);
|
wanted_frames_ = 1.0 / (snes_.memory().pal_timing() ? 50.0 : 60.0);
|
||||||
wanted_samples_ = 48000 / (snes_.memory().pal_timing() ? 50 : 60);
|
wanted_samples_ = 48000 / (snes_.memory().pal_timing() ? 50 : 60);
|
||||||
loaded = true;
|
snes_initialized_ = true;
|
||||||
|
|
||||||
count_frequency = SDL_GetPerformanceFrequency();
|
count_frequency = SDL_GetPerformanceFrequency();
|
||||||
last_count = SDL_GetPerformanceCounter();
|
last_count = SDL_GetPerformanceCounter();
|
||||||
@@ -112,7 +131,7 @@ void Emulator::Run(Rom* rom) {
|
|||||||
frames_to_process = 4;
|
frames_to_process = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loaded && frames_to_process > 0) {
|
if (snes_initialized_ && frames_to_process > 0) {
|
||||||
// Process frames (skip rendering for all but last frame if falling behind)
|
// Process frames (skip rendering for all but last frame if falling behind)
|
||||||
for (int i = 0; i < frames_to_process; i++) {
|
for (int i = 0; i < frames_to_process; i++) {
|
||||||
bool should_render = (i == frames_to_process - 1);
|
bool should_render = (i == frames_to_process - 1);
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class Emulator {
|
|||||||
audio_device_ = audio_device;
|
audio_device_ = audio_device;
|
||||||
}
|
}
|
||||||
auto wanted_samples() const -> int { return wanted_samples_; }
|
auto wanted_samples() const -> int { return wanted_samples_; }
|
||||||
|
void set_renderer(gfx::IRenderer* renderer) { renderer_ = renderer; }
|
||||||
|
|
||||||
// AI Agent Integration API
|
// AI Agent Integration API
|
||||||
bool IsEmulatorReady() const { return snes_.running() && !rom_data_.empty(); }
|
bool IsEmulatorReady() const { return snes_.running() && !rom_data_.empty(); }
|
||||||
@@ -133,6 +134,7 @@ class Emulator {
|
|||||||
|
|
||||||
Snes snes_;
|
Snes snes_;
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
|
bool snes_initialized_ = false;
|
||||||
gfx::IRenderer* renderer_ = nullptr;
|
gfx::IRenderer* renderer_ = nullptr;
|
||||||
void* ppu_texture_ = nullptr;
|
void* ppu_texture_ = nullptr;
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ void Arena::QueueTextureCommand(TextureCommandType type, Bitmap* bitmap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Arena::ProcessTextureQueue(IRenderer* renderer) {
|
void Arena::ProcessTextureQueue(IRenderer* renderer) {
|
||||||
if (!renderer_) return;
|
if (!renderer_ || texture_command_queue_.empty()) return;
|
||||||
|
|
||||||
for (const auto& command : texture_command_queue_) {
|
for (const auto& command : texture_command_queue_) {
|
||||||
switch (command.type) {
|
switch (command.type) {
|
||||||
|
|||||||
@@ -60,6 +60,16 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual TextureHandle CreateTexture(int width, int height) = 0;
|
virtual TextureHandle CreateTexture(int width, int height) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a new texture with a specific pixel format.
|
||||||
|
* @param width The width of the texture in pixels.
|
||||||
|
* @param height The height of the texture in pixels.
|
||||||
|
* @param format The SDL pixel format (e.g., SDL_PIXELFORMAT_ARGB8888).
|
||||||
|
* @param access The texture access pattern (e.g., SDL_TEXTUREACCESS_STREAMING).
|
||||||
|
* @return An abstract TextureHandle to the newly created texture, or nullptr on failure.
|
||||||
|
*/
|
||||||
|
virtual TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format, int access) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Updates a texture with the pixel data from a Bitmap.
|
* @brief Updates a texture with the pixel data from a Bitmap.
|
||||||
* @param texture The handle of the texture to update.
|
* @param texture The handle of the texture to update.
|
||||||
|
|||||||
@@ -50,6 +50,16 @@ TextureHandle SDL2Renderer::CreateTexture(int width, int height) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates an SDL_Texture with a specific pixel format and access pattern.
|
||||||
|
* This is useful for specialized textures like emulator PPU output.
|
||||||
|
*/
|
||||||
|
TextureHandle SDL2Renderer::CreateTextureWithFormat(int width, int height, uint32_t format, int access) {
|
||||||
|
return static_cast<TextureHandle>(
|
||||||
|
SDL_CreateTexture(renderer_.get(), format, access, width, height)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Updates an SDL_Texture with data from a Bitmap.
|
* @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.
|
* This involves converting the bitmap's surface to the correct format and updating the texture.
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public:
|
|||||||
|
|
||||||
// --- Texture Management ---
|
// --- Texture Management ---
|
||||||
TextureHandle CreateTexture(int width, int height) override;
|
TextureHandle CreateTexture(int width, int height) override;
|
||||||
|
TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format, int access) override;
|
||||||
void UpdateTexture(TextureHandle texture, const Bitmap& bitmap) override;
|
void UpdateTexture(TextureHandle texture, const Bitmap& bitmap) override;
|
||||||
void DestroyTexture(TextureHandle texture) override;
|
void DestroyTexture(TextureHandle texture) override;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user