From 8e90b8a103a39784be64512c7ff31eea93d77ada Mon Sep 17 00:00:00 2001
From: wheremyfoodat <gponiris2004@gmail.com>
Date: Tue, 13 Jun 2023 22:47:01 +0300
Subject: [PATCH] [GPU] Bottom screen stub

---
 include/memory.hpp                    |  3 +
 include/renderer_gl/renderer_gl.hpp   |  3 +
 include/renderer_gl/surface_cache.hpp | 10 ++++
 src/core/renderer_gl/renderer_gl.cpp  | 83 ++++++++++++++++++++-------
 src/core/services/gsp_gpu.cpp         | 26 ++++++++-
 src/emulator.cpp                      |  1 -
 6 files changed, 101 insertions(+), 25 deletions(-)

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<u32, regNum>& 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 <typename SurfaceType, size_t capacity>
 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