// // Created by ryuuji on 6/20/25. // #include "Graphics.h" #include #include #include "imgui.h" #include "imgui_impl_sdl3.h" #include "imgui_impl_sdlrenderer3.h" void SDLWindowDestroyer::operator()(SDL_Window* window) const { std::cout << "Destroying window" << std::endl; SDL_DestroyWindow(window); } void SDLRendererDestroyer::operator()(SDL_Renderer* renderer) const { std::cout << "Destroying renderer" << std::endl; SDL_DestroyRenderer(renderer); } void SDLTextureDestroyer::operator()(SDL_Texture* texture) const { std::cout << "Destroying texture" << std::endl; SDL_DestroyTexture(texture); } Graphics::Graphics(std::shared_ptr machine_state): machine_state{std::move(machine_state)}, window_width{1920}, window_height{1080}, chip8_width(64), chip8_height(32), main_scale(1.0f) {} bool Graphics::init_sdl() { SDL_SetAppMetadata("CHIP-8 Emulator", "0.0.1", "fun.skrd.chip8"); if (!SDL_Init(SDL_INIT_VIDEO)) { SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); return false; } SDL_Window* raw_window = nullptr; SDL_Renderer* raw_renderer = nullptr; main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); SDL_WindowFlags window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; if (!SDL_CreateWindowAndRenderer("CHIP-8 Emulator", window_width * main_scale, window_height * main_scale, window_flags, &raw_window, &raw_renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return false; } this->window = std::unique_ptr(raw_window); this->renderer = std::unique_ptr(raw_renderer); SDL_SetRenderVSync(renderer.get(), 1); SDL_SetWindowPosition(window.get(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); SDL_ShowWindow(window.get()); SDL_Texture* raw_texture = SDL_CreateTexture(renderer.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, chip8_width, chip8_height); this->texture = std::unique_ptr(raw_texture); SDL_SetTextureScaleMode(texture.get(), SDL_SCALEMODE_NEAREST); return true; } bool Graphics::init_imgui() { IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; ImGui::StyleColorsDark(); ImGuiStyle& style = ImGui::GetStyle(); style.ScaleAllSizes(main_scale); style.FontScaleDpi = main_scale; ImGui_ImplSDL3_InitForSDLRenderer(window.get(), renderer.get()); ImGui_ImplSDLRenderer3_Init(renderer.get()); return true; } bool Graphics::init() { if (!init_sdl()) { return false; } if (!init_imgui()) { return false; } return true; } void Graphics::draw() { if (SDL_GetWindowFlags(window.get()) & SDL_WINDOW_MINIMIZED) { SDL_Delay(10); } build_chip8_texture(); ImGuiIO& io = ImGui::GetIO(); ImGui_ImplSDLRenderer3_NewFrame(); ImGui_ImplSDL3_NewFrame(); ImGui::NewFrame(); draw_chip8_widget(); ImGui::Render(); SDL_SetRenderScale(renderer.get(), io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); SDL_SetRenderDrawColor(renderer.get(), 0, 0, 0, 0xFF); SDL_RenderClear(renderer.get()); ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer.get()); SDL_RenderPresent(renderer.get()); } void Graphics::build_chip8_texture() { SDL_Surface* surface = nullptr; auto& display = machine_state->display; if (SDL_LockTextureToSurface(texture.get(), nullptr, &surface)) { SDL_FillSurfaceRect(surface, nullptr, SDL_MapRGB(SDL_GetPixelFormatDetails(surface->format), nullptr, 0, 0, 0)); for (int i = 0; i < display.size(); i++) { if (display[i]) { int x = (i % chip8_width); int y = (i / chip8_width); SDL_Rect rect = {x, y, 1, 1}; Uint32 color = SDL_MapRGB( SDL_GetPixelFormatDetails(surface->format), nullptr, 0, 255, 0 ); SDL_FillSurfaceRect(surface, &rect, color); } } SDL_UnlockTexture(texture.get()); } } void Graphics::draw_chip8_widget() { ImGui::Begin("CHIP-8"); const ImVec2 available_size = ImGui::GetContentRegionAvail(); int scale_x = static_cast(available_size.x / chip8_width); int scale_y = static_cast(available_size.y / chip8_height); int scale = std::max(1, std::min(scale_x, scale_y)); ImVec2 scaled_size(chip8_width * scale, chip8_height * scale); ImVec2 cursor_pos = ImGui::GetCursorPos(); ImVec2 center_offset((available_size.x - scaled_size.x) / 2, (available_size.y - scaled_size.y) / 2); ImGui::SetCursorPos(ImVec2(cursor_pos.x + center_offset.x, cursor_pos.y + center_offset.y)); ImGui::Image((ImTextureID)(intptr_t)texture.get(), scaled_size); ImGui::End(); }