diff --git a/include/memory.hpp b/include/memory.hpp index 33b18ca5..ac71e7f3 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -33,7 +33,10 @@ namespace VirtualAddrs { NormalHeapStart = 0x08000000, LinearHeapStartOld = 0x14000000, // If kernel version < 0x22C + LinearHeapEndOld = 0x1C000000, + LinearHeapStartNew = 0x30000000, + LinearHeapEndNew = 0x40000000, // Start of TLS for first thread. Next thread's storage will be at TLSBase + 0x1000, and so on TLSBase = 0xFF400000, diff --git a/include/renderer_gl/renderer_gl.hpp b/include/renderer_gl/renderer_gl.hpp index ac11a813..2891db44 100644 --- a/include/renderer_gl/renderer_gl.hpp +++ b/include/renderer_gl/renderer_gl.hpp @@ -57,6 +57,9 @@ class Renderer { static constexpr u32 regNum = 0x300; // Number of internal PICA registers const std::array& regs; + OpenGL::Texture screenTexture; + OpenGL::Framebuffer screenFramebuffer; + OpenGL::Framebuffer getColourFBO(); OpenGL::Texture getTexture(Texture& tex); diff --git a/include/renderer_gl/surface_cache.hpp b/include/renderer_gl/surface_cache.hpp index 97a6d05e..e6b07763 100644 --- a/include/renderer_gl/surface_cache.hpp +++ b/include/renderer_gl/surface_cache.hpp @@ -13,6 +13,7 @@ // Are concerned. We could overload the == operator, but that implies full equality // Including equality of the allocated OpenGL resources, which we don't want // - A "valid" member that tells us whether the function is still valid or not +// - A "location" member which tells us which location in 3DS memory this surface occupies template class SurfaceCache { // Vanilla std::optional can't hold actual references @@ -40,6 +41,15 @@ public: return std::nullopt; } + OptionalRef findFromAddress(u32 address) { + for (auto& e : buffer) { + if (e.location == address && e.valid) + return e; + } + + return std::nullopt; + } + // Adds a surface object to the cache and returns it SurfaceType& add(const SurfaceType& surface) { if (size >= capacity) { diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 6da0dd70..81563563 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -187,8 +187,8 @@ void Renderer::initGraphicsContext() { OpenGL::Shader vertDisplay(displayVertexShader, OpenGL::Vertex); OpenGL::Shader fragDisplay(displayFragmentShader, OpenGL::Fragment); displayProgram.create({ vertDisplay, fragDisplay }); - displayProgram.use(); + displayProgram.use(); glUniform1i(OpenGL::uniformLocation(displayProgram, "u_texture"), 0); // Init sampler object vbo.createFixedSize(sizeof(Vertex) * vertexBufferSize, GL_STREAM_DRAW); @@ -208,18 +208,35 @@ void Renderer::initGraphicsContext() { dummyVBO.create(); dummyVAO.create(); + + // Create texture and framebuffer for the 3DS screen + const u32 screenTextureWidth = 2 * 400; // Top screen is 400 pixels wide, bottom is 320 + const u32 screenTextureHeight = 2 * 240; // Both screens are 240 pixels tall + + auto prevTexture = OpenGL::getTex2D(); + screenTexture.create(screenTextureWidth, screenTextureHeight, GL_RGBA8); + screenTexture.bind(); + screenTexture.setMinFilter(OpenGL::Linear); + screenTexture.setMagFilter(OpenGL::Linear); + glBindTexture(GL_TEXTURE_2D, prevTexture); + + screenFramebuffer.createWithDrawTexture(screenTexture); + screenFramebuffer.bind(OpenGL::DrawAndReadFramebuffer); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + Helpers::panic("Incomplete framebuffer"); + + // TODO: This should not clear the framebuffer contents. It should load them from VRAM. + GLint oldViewport[4]; + glGetIntegerv(GL_VIEWPORT, oldViewport); + OpenGL::setViewport(screenTextureWidth, screenTextureHeight); + OpenGL::setClearColor(0.0, 0.0, 0.0, 1.0); + OpenGL::clearColor(); + OpenGL::setViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]); + reset(); } -void Renderer::getGraphicsContext() { - OpenGL::disableScissor(); - OpenGL::setViewport(400, 240); - - vbo.bind(); - vao.bind(); - triangleProgram.use(); -} - // Set up the OpenGL blending context to match the emulated PICA void Renderer::setupBlending() { const bool blendingEnabled = (regs[PICAInternalRegs::ColourOperation] & (1 << 8)) != 0; @@ -266,6 +283,12 @@ void Renderer::setupBlending() { } void Renderer::drawVertices(OpenGL::Primitives primType, Vertex* vertices, u32 count) { + OpenGL::disableScissor(); + + vbo.bind(); + vao.bind(); + triangleProgram.use(); + // Adjust alpha test if necessary const u32 alphaControl = regs[PICAInternalRegs::AlphaTestConfig]; if (alphaControl != oldAlphaControl) { @@ -361,20 +384,11 @@ constexpr u32 bottomScreenBuffer = 0x1f05dc00; // Quick hack to display top screen for now void Renderer::display() { - OpenGL::disableBlend(); - OpenGL::disableDepth(); OpenGL::disableScissor(); - OpenGL::bindScreenFramebuffer(); - colourBufferCache[0].texture.bind(); - - displayProgram.use(); - - dummyVAO.bind(); - OpenGL::setClearColor(0.0, 0.0, 1.0, 1.0); // Clear screen colour - OpenGL::clearColor(); - OpenGL::setViewport(0, 240, 400, 240); // Actually draw our 3DS screen - OpenGL::draw(OpenGL::TriangleStrip, 4); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + screenFramebuffer.bind(OpenGL::ReadFramebuffer); + glBlitFramebuffer(0, 0, 400, 480, 0, 0, 400, 480, GL_COLOR_BUFFER_BIT, GL_LINEAR); } void Renderer::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) { @@ -408,6 +422,7 @@ OpenGL::Framebuffer Renderer::getColourFBO() { if (buffer.has_value()) { return buffer.value().get().fbo; } else { + printf("New colour buffer: %08X\n", colourBufferLoc); return colourBufferCache.add(sampleBuffer).fbo; } } @@ -450,4 +465,28 @@ void Renderer::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 const u32 outputWidth = outputSize & 0xffff; const u32 outputGap = outputSize >> 16; + + auto framebuffer = colourBufferCache.findFromAddress(inputAddr); + if (framebuffer.has_value()) { + OpenGL::Texture& texture = framebuffer.value().get().texture; + texture.bind(); + screenFramebuffer.bind(OpenGL::DrawFramebuffer); + + OpenGL::disableBlend(); + OpenGL::disableDepth(); + OpenGL::disableScissor(); + + displayProgram.use(); + + // Hack: Detect whether we are writing to the top or bottom screen by checking output gap and drawing to the proper part of the output texture + // We consider output gap == 320 to mean bottom, and anything else to mean top + if (outputGap == 320) { + OpenGL::setViewport(40, 0, 320, 240); + } else { + OpenGL::setViewport(0, 240, 400, 240); + } + + dummyVAO.bind(); + OpenGL::draw(OpenGL::TriangleStrip, 4); // Actually draw our 3DS screen + } } \ No newline at end of file diff --git a/src/core/services/gsp_gpu.cpp b/src/core/services/gsp_gpu.cpp index 76d2f4a5..9c0df876 100644 --- a/src/core/services/gsp_gpu.cpp +++ b/src/core/services/gsp_gpu.cpp @@ -331,9 +331,31 @@ void GPUService::memoryFill(u32* cmd) { } } +static u32 VaddrToPaddr(u32 addr) { + if (addr >= VirtualAddrs::VramStart && addr < (VirtualAddrs::VramStart + VirtualAddrs::VramSize)) [[likely]] { + return addr - VirtualAddrs::VramStart + PhysicalAddrs::VRAM; + } + + else if (addr >= VirtualAddrs::LinearHeapStartOld && addr < VirtualAddrs::LinearHeapEndOld) { + return addr - VirtualAddrs::LinearHeapStartOld + PhysicalAddrs::FCRAM; + } + + else if (addr >= VirtualAddrs::LinearHeapStartNew && addr < VirtualAddrs::LinearHeapEndNew) { + return addr - VirtualAddrs::LinearHeapStartNew + PhysicalAddrs::FCRAM; + } + + else if (addr == 0) { + return 0; + } + + Helpers::warn("[GSP::GPU VaddrToPaddr] Unknown virtual address %08X", addr); + // Obviously garbage address + return 0xF3310932; +} + void GPUService::triggerDisplayTransfer(u32* cmd) { - const u32 inputAddr = cmd[1]; - const u32 outputAddr = cmd[2]; + const u32 inputAddr = VaddrToPaddr(cmd[1]); + const u32 outputAddr = VaddrToPaddr(cmd[2]); const u32 inputSize = cmd[3]; const u32 outputSize = cmd[4]; const u32 flags = cmd[5]; diff --git a/src/emulator.cpp b/src/emulator.cpp index 8141a94a..52023fdd 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -23,7 +23,6 @@ void Emulator::render() { void Emulator::run() { while (running) { - gpu.getGraphicsContext(); // Give the GPU a rendering context runFrame(); // Run 1 frame of instructions gpu.display(); // Display graphics