diff --git a/include/PICA/gpu.hpp b/include/PICA/gpu.hpp
index db61a0f9..7a80c633 100644
--- a/include/PICA/gpu.hpp
+++ b/include/PICA/gpu.hpp
@@ -47,9 +47,11 @@ class GPU {
 			u32 index = paddr - PhysicalAddrs::FCRAM;
 
 			return (T*)&fcram[index];
-		}
-		else {
-			Helpers::panic("[PICA] Pointer to unimplemented paddr %08X", paddr);
+		} else if (paddr >= PhysicalAddrs::VRAM && paddr <= PhysicalAddrs::VRAMEnd) {
+			u32 index = paddr - PhysicalAddrs::VRAM;
+			return (T*)&vram[index];
+		} else [[unlikely]] {
+			Helpers::panic("[GPU] Tried to access unknown physical address: %08X", paddr);
 		}
 	}
 
@@ -102,6 +104,7 @@ public:
 	void getGraphicsContext(); // Set up the graphics context for rendering
 	void display(); // Display the screen contents onto our window
 
+	void fireDMA(u32 dest, u32 source, u32 size);
 	void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control);
 	void reset();
 
diff --git a/include/memory.hpp b/include/memory.hpp
index a15adf0c..a3815534 100644
--- a/include/memory.hpp
+++ b/include/memory.hpp
@@ -11,6 +11,8 @@
 
 namespace PhysicalAddrs {
 	enum : u32 {
+		VRAM = 0x18000000,
+		VRAMEnd = VRAM + 0x005FFFFF,
 		FCRAM = 0x20000000,
 		FCRAMEnd = FCRAM + 0x07FFFFFF
 	};
@@ -38,6 +40,7 @@ namespace VirtualAddrs {
 
 		VramStart = 0x1F000000,
 		VramSize = 0x00600000,
+		FcramTotalSize = 128_MB,
 		DSPMemStart = 0x1FF00000
 	};
 }
diff --git a/src/core/PICA/gpu.cpp b/src/core/PICA/gpu.cpp
index 7c109ad9..3e02280f 100644
--- a/src/core/PICA/gpu.cpp
+++ b/src/core/PICA/gpu.cpp
@@ -158,4 +158,25 @@ void GPU::drawArrays() {
 	};
 	const auto shape = primTypes[primType];
 	drawVertices(shape, vertices, vertexCount);
+}
+
+void GPU::fireDMA(u32 dest, u32 source, u32 size) {
+	printf("[GPU] DMA of %08X bytes from %08X to %08X\n", size, source, dest);
+	constexpr u32 vramStart = VirtualAddrs::VramStart;
+	constexpr u32 vramSize = VirtualAddrs::VramSize;
+
+	const u32 fcramStart = mem.getLinearHeapVaddr();
+	constexpr u32 fcramSize = VirtualAddrs::FcramTotalSize;
+
+	if (dest - vramStart >= vramSize || size > (vramSize - (dest - vramStart))) [[unlikely]] {
+		Helpers::panic("GPU DMA does not target VRAM");
+	}
+
+	if (source - fcramStart >= fcramSize || size > (fcramSize - (dest - fcramStart))) {
+		Helpers::panic("GPU DMA does not have FCRAM as its source");
+	}
+
+	// Valid, optimized FCRAM->VRAM DMA. TODO: Is VRAM->VRAM DMA allowed?
+	u8* fcram = mem.getFCRAM();
+	std::memcpy(&vram[dest - vramStart], &fcram[source - fcramStart], size);
 }
\ No newline at end of file
diff --git a/src/core/services/gsp_gpu.cpp b/src/core/services/gsp_gpu.cpp
index 61c970ac..910f7aa9 100644
--- a/src/core/services/gsp_gpu.cpp
+++ b/src/core/services/gsp_gpu.cpp
@@ -278,7 +278,8 @@ void GPUService::triggerDMARequest(u32* cmd) {
 	u32 size = cmd[3];
 	bool flush = cmd[7] == 1;
 
-	log("GSP::GPU::TriggerDMARequest (source = %08X, dest = %08X, size = %08X) (Unimplemented)\n", source, dest, size);
+	log("GSP::GPU::TriggerDMARequest (source = %08X, dest = %08X, size = %08X)\n", source, dest, size);
+	gpu.fireDMA(dest, source, size);
 	requestInterrupt(GPUInterrupt::DMA);
 }