From f9f401c6c0b9f382710e48fa2734548544390391 Mon Sep 17 00:00:00 2001
From: Lauchmelder <robert.altner11@gmail.com>
Date: Thu, 3 Mar 2022 02:33:08 +0100
Subject: [PATCH] started ppu

---
 roms/donkeykong.nes                    | Bin 0 -> 24592 bytes
 src/Application.cpp                    |  40 ++++++-
 src/Application.hpp                    |   4 +
 src/Bus.cpp                            |   6 +-
 src/Bus.hpp                            |   2 +-
 src/CMakeLists.txt                     |   2 +-
 src/ControllerPort.cpp                 |   3 +
 src/PPU.cpp                            |  90 +++++++++------
 src/PPU.hpp                            |  12 +-
 src/controllers/StandardController.cpp |   5 -
 src/debugger/ControllerPortViewer.cpp  |   4 +-
 src/gfx/Screen.cpp                     | 154 +++++++++++++++++++++++++
 src/gfx/Screen.hpp                     |  34 ++++++
 src/gfx/Window.cpp                     |   7 ++
 src/gfx/Window.hpp                     |   4 +
 15 files changed, 313 insertions(+), 54 deletions(-)
 create mode 100644 roms/donkeykong.nes
 create mode 100644 src/gfx/Screen.cpp
 create mode 100644 src/gfx/Screen.hpp

diff --git a/roms/donkeykong.nes b/roms/donkeykong.nes
new file mode 100644
index 0000000000000000000000000000000000000000..ecd6f0a740de52e799a814dd9f14aef4a811e7d2
GIT binary patch
literal 24592
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

literal 0
HcmV?d00001

diff --git a/src/Application.cpp b/src/Application.cpp
index 968f7c4..fcb52e3 100644
--- a/src/Application.cpp
+++ b/src/Application.cpp
@@ -10,6 +10,7 @@
 
 #include "Log.hpp"
 #include "Bus.hpp"
+#include "Screen.hpp"
 #include "Debugger.hpp"
 #include "gfx/Window.hpp"
 
@@ -48,7 +49,7 @@ Application::Application() :
 	LOG_CORE_INFO("Creating window");
 	try
 	{
-		window = new Window(1280, 720, "NES Emulator");
+		window = new Window(256, 240, "NES Emulator");
 	}
 	catch (const std::runtime_error& err)
 	{
@@ -60,6 +61,8 @@ Application::Application() :
 	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
 		throw std::runtime_error("Failed to set up OpenGL loader");
 	
+	window->SetScale(scale);
+
 	LOG_CORE_INFO("Setting up ImGui");
 	IMGUI_CHECKVERSION();
 	ImGui::CreateContext();
@@ -80,7 +83,17 @@ Application::Application() :
 		style.Colors[ImGuiCol_WindowBg].w = 1.0f;
 	}
 
-	bus = new Bus;
+	try
+	{
+		screen = new Screen;
+	}
+	catch(const std::runtime_error& err)
+	{
+		LOG_CORE_ERROR("Screen creation failed");
+		throw err;
+	}
+
+	bus = new Bus(screen);
 	debugger = new Debugger(bus);
 }
 
@@ -96,12 +109,35 @@ Application::~Application()
 
 bool Application::Update()
 {
+	if (window->GetScale() != scale)
+		window->SetScale(scale);
+
 	glfwPollEvents();
 
 	if (!debugger->Update())
 		return false;
 
 	window->Begin();
+	screen->Render();
+
+	if (ImGui::BeginMainMenuBar())
+	{
+		if (ImGui::BeginMenu("Screen"))
+		{
+			if (ImGui::BeginMenu("Scale"))
+			{
+				ImGui::RadioButton("x1", &scale, 1);
+				ImGui::RadioButton("x2", &scale, 2);
+				ImGui::RadioButton("x3", &scale, 3);
+				ImGui::RadioButton("x4", &scale, 4);
+				ImGui::EndMenu();
+			}
+			ImGui::EndMenu();
+		}
+
+		ImGui::EndMainMenuBar();
+	}
+
 	debugger->Render();
 	window->End();
 
diff --git a/src/Application.hpp b/src/Application.hpp
index 4ed7b95..1001bd4 100644
--- a/src/Application.hpp
+++ b/src/Application.hpp
@@ -3,6 +3,7 @@
 class Bus;
 class Window;
 class Debugger;
+class Screen;
 
 /**
  * @brief Contains the program loop and invokes other objects update functions.
@@ -26,7 +27,10 @@ private:
 	bool Update();
 
 private:
+	int scale = 3;
+
 	Window* window;
 	Bus* bus;
+	Screen* screen;
 	Debugger* debugger;
 };
diff --git a/src/Bus.cpp b/src/Bus.cpp
index d26379b..5e7d069 100644
--- a/src/Bus.cpp
+++ b/src/Bus.cpp
@@ -5,8 +5,8 @@
 
 #include "controllers/StandardController.hpp"
 
-Bus::Bus() :
-	cpu(this), ppu(this), cartridge(this)
+Bus::Bus(Screen* screen) :
+	cpu(this), ppu(this, screen), cartridge(this)
 {
 	LOG_CORE_INFO("Allocating RAM");
 	RAM = std::vector<Byte>(0x800);
@@ -15,7 +15,7 @@ Bus::Bus() :
 	VRAM = std::vector<Byte>(0x800);
 
 	LOG_CORE_INFO("Inserting cartridge");
-	cartridge.Load("roms/nestest.nes");
+	cartridge.Load("roms/donkeykong.nes");
 
 	LOG_CORE_INFO("Powering up CPU");
 	cpu.Powerup();
diff --git a/src/Bus.hpp b/src/Bus.hpp
index 7fd5b7c..a161782 100644
--- a/src/Bus.hpp
+++ b/src/Bus.hpp
@@ -23,7 +23,7 @@ class Bus
 	friend class NametableViewer;
 
 public:
-	Bus();
+	Bus(Screen* screen);
 
 	/**
 	 * @brief Reboot the NES.
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 3bc6206..ec20574 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -13,7 +13,7 @@ add_executable(nesemu
 	"debugger/PPUWatcher.cpp" 
 	"debugger/Disassembler.cpp" 
 	"debugger/MemoryViewer.cpp" 
-	"debugger/NametableViewer.cpp"  "ControllerPort.cpp" "controllers/StandardController.cpp" "gfx/Input.cpp" "debugger/ControllerPortViewer.cpp")
+	"debugger/NametableViewer.cpp"  "ControllerPort.cpp" "controllers/StandardController.cpp" "gfx/Input.cpp" "debugger/ControllerPortViewer.cpp" "gfx/Screen.cpp")
 
 target_include_directories(nesemu PRIVATE
 	mappers
diff --git a/src/ControllerPort.cpp b/src/ControllerPort.cpp
index f08f3b6..046d813 100644
--- a/src/ControllerPort.cpp
+++ b/src/ControllerPort.cpp
@@ -28,6 +28,9 @@ Byte ControllerPort::Write(Word addr, Byte val)
 
 Byte ControllerPort::Read(Word addr)
 {
+	if (connectedDevices[addr & 1] == nullptr)
+		return 0xFF;
+
 	return connectedDevices[addr & 1]->CLK();
 }
 
diff --git a/src/PPU.cpp b/src/PPU.cpp
index a0c0205..9ef5ebd 100644
--- a/src/PPU.cpp
+++ b/src/PPU.cpp
@@ -2,9 +2,12 @@
 #include "Log.hpp"
 #include "Bus.hpp"
 
-PPU::PPU(Bus* bus) :
-	bus(bus), ppuctrl{0}, ppustatus{0}
+#include "gfx/Screen.hpp"
+
+PPU::PPU(Bus* bus, Screen* screen) :
+	bus(bus), screen(screen), ppuctrl{ 0 }, ppustatus{ 0 }
 {
+	LOG_CORE_INFO("{0}", sizeof(VRAMAddress));
 }
 
 void PPU::Powerup()
@@ -57,38 +60,6 @@ void PPU::Tick()
 			y = 0;
 	}
 
-	current.CoarseX++;
-	if (current.CoarseX > 31)
-	{
-		current.CoarseX = 0;
-		current.NametableSel ^= 0x1;
-	}
-
-	if (x == 256)
-	{
-		if (current.FineY < 7)
-		{
-			current.FineY++;
-		}
-		else
-		{
-			current.FineY = 0;
-			if (current.CoarseY == 29)
-			{
-				current.CoarseY = 0;
-				current.NametableSel ^= 0x2;
-			}
-			else if (current.CoarseY == 31)
-			{
-				current.CoarseY = 0;
-			}
-			else
-			{
-				current.CoarseY++;
-			}
-		}
-	}
-
 	UpdateState();
 
 	// On this cycle the VBlankStarted bit is set in the ppustatus
@@ -113,6 +84,14 @@ void PPU::Tick()
 		PerformRenderAction();
 	}
 	
+	if (x < 256 && y < 240)
+	{
+		uint8_t xOffset = 7 - fineX;
+
+		uint8_t color = (((patternTableHi >> xOffset) & 0x1) << 1) | ((patternTableLo >> xOffset) & 0x1);
+		color *= 80;
+		screen->SetPixel(x, y, { color, color, color});
+	}
 }
 
 Byte PPU::ReadRegister(Byte id)
@@ -283,6 +262,12 @@ void PPU::UpdateState()
 void PPU::PerformRenderAction()
 {
 	if (cycleType == CycleType::Idle)
+	{
+		fineX = 0;
+		return;
+	}
+
+	if (cycleType == CycleType::SpriteFetching)
 		return;
 
 	if (memoryAccessLatch == 1)
@@ -302,16 +287,49 @@ void PPU::PerformRenderAction()
 			break;
 
 		case FetchingPhase::PatternTableLo:
-			patternTableLo = Read(ppuctrl.Flag.BackgrPatternTableAddr | nametableByte);
+			patternTableLo = Read(((Word)ppuctrl.Flag.BackgrPatternTableAddr << 12) | ((Word)nametableByte << 4) + current.FineY);
 			fetchPhase = FetchingPhase::PatternTableHi;
 			break;
 
 		case FetchingPhase::PatternTableHi:
-			patternTableLo = Read((ppuctrl.Flag.BackgrPatternTableAddr | nametableByte) + 8);
+			patternTableLo = Read((((Word)ppuctrl.Flag.BackgrPatternTableAddr << 12) | ((Word)nametableByte << 4)) + 8 + current.FineY);
+
+			current.CoarseX++;
+			if (x == 256)
+			{
+				current.CoarseX = temporary.CoarseX;
+				current.NametableSel ^= 0x1;
+
+				if (current.FineY < 7)
+				{
+					current.FineY++;
+				}
+				else
+				{
+					current.FineY = temporary.FineY;
+					if (current.CoarseY == 29)
+					{
+						current.CoarseY = temporary.CoarseY;
+						current.NametableSel ^= 0x2;
+					}
+					else if (current.CoarseY == 31)
+					{
+						current.CoarseY = temporary.CoarseY;
+					}
+					else
+					{
+						current.CoarseY++;
+					}
+				}
+			}
+
 			fetchPhase = FetchingPhase::NametableByte;
 			break;
 		}
 	}
 
+	fineX++;
+	if (fineX >= 8)
+		fineX = 0;
 	memoryAccessLatch = 1 - memoryAccessLatch;
 }
diff --git a/src/PPU.hpp b/src/PPU.hpp
index fab4445..fc06b49 100644
--- a/src/PPU.hpp
+++ b/src/PPU.hpp
@@ -3,6 +3,7 @@
 #include "Types.hpp"
 
 class Bus;
+class Screen;
 
 enum class ScanlineType
 {
@@ -33,10 +34,10 @@ union VRAMAddress
 {
 	struct
 	{
-		Byte CoarseX : 5;
-		Byte CoarseY : 5;
-		Byte NametableSel : 2;
-		Byte FineY : 3;
+		Word CoarseX : 5;
+		Word CoarseY : 5;
+		Word NametableSel : 2;
+		Word FineY : 3;
 	};
 
 	Word Raw;
@@ -50,7 +51,7 @@ class PPU
 	friend class PPUWatcher;
 
 public:
-	PPU(Bus* bus);
+	PPU(Bus* bus, Screen* screen);
 
 	/**
 	 * @brief Powerup PPU.
@@ -177,4 +178,5 @@ private:
 	uint8_t memoryAccessLatch = 0;
 	bool isFrameDone = false;
 	Bus* bus;
+	Screen* screen;
 };
diff --git a/src/controllers/StandardController.cpp b/src/controllers/StandardController.cpp
index 0fa58a3..ac1f2ee 100644
--- a/src/controllers/StandardController.cpp
+++ b/src/controllers/StandardController.cpp
@@ -22,9 +22,4 @@ void StandardController::OUT(PortLatch latch)
 	pressed.Buttons.Down	= Input::IsKeyDown(GLFW_KEY_DOWN);
 	pressed.Buttons.Left	= Input::IsKeyDown(GLFW_KEY_LEFT);
 	pressed.Buttons.Right	= Input::IsKeyDown(GLFW_KEY_RIGHT);
-
-	if (pressed.Raw != 0)
-		volatile int jdfi = 3;
-
-	outRegister = pressed.Raw;
 }
diff --git a/src/debugger/ControllerPortViewer.cpp b/src/debugger/ControllerPortViewer.cpp
index f18b213..a5b7286 100644
--- a/src/debugger/ControllerPortViewer.cpp
+++ b/src/debugger/ControllerPortViewer.cpp
@@ -34,7 +34,7 @@ void ControllerPortViewer::OnRender()
 			ImGui::Separator();
 
 			ImGui::InputScalar("Shift Register", ImGuiDataType_U8, &controller->outRegister, (const void*)0, (const void*)0, "%02X", ImGuiInputTextFlags_CharsHexadecimal);
-			
+
 			if (ImGui::BeginTable(controllerName.c_str(), 8))
 			{
 				for(int i = 0; i < 8; i++)
@@ -52,6 +52,8 @@ void ControllerPortViewer::OnRender()
 				ImGui::EndTable();
 			}
 		}
+
+		counter++;
 	}
 
 	ImGui::End();
diff --git a/src/gfx/Screen.cpp b/src/gfx/Screen.cpp
new file mode 100644
index 0000000..a8819b6
--- /dev/null
+++ b/src/gfx/Screen.cpp
@@ -0,0 +1,154 @@
+#include "Screen.hpp"
+
+#include <glad/glad.h>
+#include <string>
+#include <stdexcept>
+
+#include "../Log.hpp"
+
+Screen::Screen()
+{
+	pixels.resize(256 * 240);
+
+	LOG_CORE_INFO("Creating vertex arrays");
+	CreateVertexArray();
+
+	LOG_CORE_INFO("Creating screen texture");
+	CreateTexture();
+
+	LOG_CORE_INFO("Creating screen shader");
+	CreateShader();
+}
+
+Screen::~Screen()
+{
+	glDeleteTextures(1, &texture);
+	glDeleteProgram(shader);
+	glDeleteBuffers(1, &vbo);
+	glDeleteVertexArrays(1, &vao);
+}
+
+void Screen::SetPixel(uint16_t x, uint16_t y, Color color)
+{
+	pixels[y * 256 + x] = color;
+}
+
+void Screen::Render()
+{
+	glTextureSubImage2D(texture, 0, 0, 0, 256, 240, GL_RGB, GL_UNSIGNED_BYTE, (const void*)pixels.data());
+
+	glBindTexture(GL_TEXTURE_2D, texture);
+	glUseProgram(shader);
+	glBindVertexArray(vao);
+
+	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+}
+
+void Screen::CreateVertexArray()
+{
+	glGenVertexArrays(1, &vao);
+	glBindVertexArray(vao);
+
+	float vertices[4 * (3 + 2)] = {
+		-1.0f, -1.0f, 0.0f,		0.0f, 1.0f,
+		-1.0f,  1.0f, 0.0f,		0.0f, 0.0f,
+		 1.0f,  1.0f, 0.0f,		1.0f, 0.0f,
+		 1.0f, -1.0f, 0.0f,		1.0f, 1.0f
+	};
+
+	glGenBuffers(1, &vbo);
+	glBindBuffer(GL_ARRAY_BUFFER, vbo);
+	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
+
+	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (const void*)0);
+	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (const void*)(3 * sizeof(float)));
+
+	glEnableVertexAttribArray(0);
+	glEnableVertexAttribArray(1);
+}
+
+void Screen::CreateTexture()
+{
+	glCreateTextures(GL_TEXTURE_2D, 1, &texture);
+	glTextureStorage2D(texture, 1, GL_RGB8, 256, 240);
+
+	glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+}
+
+void Screen::CreateShader()
+{
+	GLint status;
+	shader = glCreateProgram();
+
+	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
+	const char* vertexShaderSource = R"(
+		#version 460 core
+		
+		layout (location = 0) in vec3 a_Pos;
+		layout (location = 1) in vec2 a_UV;
+
+		out vec2 uvCoords;
+
+		void main()
+		{
+			uvCoords = a_UV;
+			gl_Position = vec4(a_Pos, 1.0f);
+		}
+	)";
+	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
+	glCompileShader(vertexShader);
+	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
+	if (status == GL_FALSE)
+	{
+		char errorBuf[512];
+		glGetShaderInfoLog(vertexShader, 512, NULL, errorBuf);
+		glDeleteShader(vertexShader);
+
+		throw std::runtime_error("Vertex shader compilation error: " + std::string(errorBuf));
+	}
+
+	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
+	const char* fragmentShaderSource = R"(
+		#version 460 core
+		
+		out vec4 FragColor;
+		in vec2 uvCoords;
+
+		uniform sampler2D screen;
+
+		void main()
+		{
+			FragColor = texture(screen, uvCoords);
+		}
+	)";
+	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
+	glCompileShader(fragmentShader);
+	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);
+	if (status == GL_FALSE)
+	{
+		char errorBuf[512];
+		glGetShaderInfoLog(fragmentShader, 512, NULL, errorBuf);
+		glDeleteShader(fragmentShader);
+		glDeleteShader(vertexShader);
+
+		throw std::runtime_error("Fragment shader compilation error: " + std::string(errorBuf));
+	}
+
+	glAttachShader(shader, vertexShader);
+	glAttachShader(shader, fragmentShader);
+	glLinkProgram(shader);
+	glGetProgramiv(shader, GL_LINK_STATUS, &status);
+	if (status == GL_FALSE)
+	{
+		char errorBuf[512];
+		glGetProgramInfoLog(shader, 512, NULL, errorBuf);
+		glDeleteShader(fragmentShader);
+		glDeleteShader(vertexShader);
+
+		throw std::runtime_error("Shader link error: " + std::string(errorBuf));
+	}
+
+	glDeleteShader(fragmentShader);
+	glDeleteShader(vertexShader);
+}
diff --git a/src/gfx/Screen.hpp b/src/gfx/Screen.hpp
new file mode 100644
index 0000000..eb24ef1
--- /dev/null
+++ b/src/gfx/Screen.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <cstdint>
+#include <vector>
+
+struct Color
+{
+	uint8_t r;
+	uint8_t g;
+	uint8_t b;
+};
+
+class Screen
+{
+public:
+	Screen();
+	~Screen();
+
+	void SetPixel(uint16_t x, uint16_t y, Color color);
+	void Render();
+
+private:
+	void CreateVertexArray();
+	void CreateTexture();
+	void CreateShader();
+
+private:
+	uint32_t texture = 0;
+	uint32_t shader = 0;
+	uint32_t vao = 0;
+	uint32_t vbo = 0;
+
+	std::vector<Color> pixels;
+};
diff --git a/src/gfx/Window.cpp b/src/gfx/Window.cpp
index 6154de0..795067d 100644
--- a/src/gfx/Window.cpp
+++ b/src/gfx/Window.cpp
@@ -10,6 +10,7 @@
 Window::Window(uint16_t width, uint16_t height, const std::string& title) :
 	handle(nullptr)
 {
+	glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
 	handle = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);
 	if (handle == nullptr)
 	{
@@ -28,6 +29,12 @@ Window::~Window()
 		glfwDestroyWindow(handle);
 }
 
+void Window::SetScale(int scale)
+{
+	glfwSetWindowSize(handle, 256 * scale, 240 * scale + 20);
+	glViewport(0, 0, 256 * scale, 240 * scale);
+}
+
 void Window::Begin()
 {
 	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
diff --git a/src/gfx/Window.hpp b/src/gfx/Window.hpp
index 02fffc9..ae9191a 100644
--- a/src/gfx/Window.hpp
+++ b/src/gfx/Window.hpp
@@ -12,12 +12,16 @@ public:
 	Window(uint16_t width, uint16_t height, const std::string& title);
 	~Window();
 
+	inline int GetScale() { return scale; }
 	inline bool ShouldClose() { return glfwWindowShouldClose(handle); }
 	inline GLFWwindow* GetNativeWindow() { return handle; }
 	
+	void SetScale(int scale);
+
 	void Begin();
 	void End();
 
 private:
+	int scale = -1;
 	GLFWwindow* handle;
 };