feat: use opengl
TODO: - Texture isn't scaling properly - I need to reverse the colours in the frag shader
This commit is contained in:
		| @@ -31,6 +31,9 @@ pub fn build(b: *std.build.Builder) void { | ||||
|     // TOML Library | ||||
|     exe.addPackagePath("toml", "lib/zig-toml/src/toml.zig"); | ||||
|  | ||||
|     // OpenGL Bindings | ||||
|     exe.addPackagePath("gl", "lib/gl_4v5.zig"); | ||||
|  | ||||
|     // Zig SDL Bindings: https://github.com/MasterQ32/SDL.zig | ||||
|     const sdk = Sdk.init(b); | ||||
|     sdk.link(exe, .dynamic); | ||||
|   | ||||
							
								
								
									
										9263
									
								
								lib/gl_4v5.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9263
									
								
								lib/gl_4v5.zig
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										145
									
								
								src/platform.zig
									
									
									
									
									
								
							
							
						
						
									
										145
									
								
								src/platform.zig
									
									
									
									
									
								
							| @@ -1,5 +1,6 @@ | ||||
| const std = @import("std"); | ||||
| const SDL = @import("sdl2"); | ||||
| const gl = @import("gl"); | ||||
| const emu = @import("core/emu.zig"); | ||||
| const config = @import("config.zig"); | ||||
|  | ||||
| @@ -11,21 +12,43 @@ const FpsTracker = @import("util.zig").FpsTracker; | ||||
| const span = @import("util.zig").span; | ||||
|  | ||||
| const pitch = @import("core/ppu.zig").framebuf_pitch; | ||||
| const gba_width = @import("core/ppu.zig").width; | ||||
| const gba_height = @import("core/ppu.zig").height; | ||||
|  | ||||
| const default_title: []const u8 = "ZBA"; | ||||
|  | ||||
| pub const Gui = struct { | ||||
|     const Self = @This(); | ||||
|     const SDL_GLContext = *anyopaque; // SDL.SDL_GLContext is a ?*anyopaque | ||||
|     const log = std.log.scoped(.Gui); | ||||
|  | ||||
|     // zig fmt: off | ||||
|     const vertices: [32]f32 = [_]f32{ | ||||
|         // Positions        // Colours      // Texture Coords | ||||
|          1.0,  1.0, 0.0,    1.0, 0.0, 0.0,  1.0, 1.0, // Top Right | ||||
|          1.0, -1.0, 0.0,    0.0, 1.0, 0.0,  1.0, 0.0, // Bottom Right | ||||
|         -1.0, -1.0, 0.0,    0.0, 0.0, 1.0,  0.0, 0.0, // Bottom Left | ||||
|         -1.0,  1.0, 0.0,    1.0, 1.0, 0.0,  0.0, 1.0, // Top Left | ||||
|     }; | ||||
|  | ||||
|     const indices: [6]u32 = [_]u32{ | ||||
|         0, 1, 3, // First Triangle | ||||
|         1, 2, 3, // Second Triangle | ||||
|     }; | ||||
|     // zig fmt: on | ||||
|  | ||||
|     window: *SDL.SDL_Window, | ||||
|     ctx: SDL_GLContext, | ||||
|     title: []const u8, | ||||
|     renderer: *SDL.SDL_Renderer, | ||||
|     texture: *SDL.SDL_Texture, | ||||
|     audio: Audio, | ||||
|  | ||||
|     program_id: gl.GLuint, | ||||
|  | ||||
|     pub fn init(title: *const [12]u8, apu: *Apu, width: i32, height: i32) Self { | ||||
|         const ret = SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_GAMECONTROLLER); | ||||
|         if (ret < 0) panic(); | ||||
|         if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO) < 0) panic(); | ||||
|         if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GL_CONTEXT_PROFILE_CORE) < 0) panic(); | ||||
|         if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 4) < 0) panic(); | ||||
|         if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 4) < 0) panic(); | ||||
|  | ||||
|         const win_scale = @intCast(c_int, config.config().host.win_scale); | ||||
|  | ||||
| @@ -35,29 +58,98 @@ pub const Gui = struct { | ||||
|             SDL.SDL_WINDOWPOS_CENTERED, | ||||
|             @as(c_int, width * win_scale), | ||||
|             @as(c_int, height * win_scale), | ||||
|             SDL.SDL_WINDOW_SHOWN, | ||||
|             SDL.SDL_WINDOW_OPENGL | SDL.SDL_WINDOW_SHOWN, | ||||
|         ) orelse panic(); | ||||
|  | ||||
|         const renderer_flags = SDL.SDL_RENDERER_ACCELERATED | if (config.config().host.vsync) SDL.SDL_RENDERER_PRESENTVSYNC else 0; | ||||
|         const renderer = SDL.SDL_CreateRenderer(window, -1, @bitCast(u32, renderer_flags)) orelse panic(); | ||||
|         const ctx = SDL.SDL_GL_CreateContext(window) orelse panic(); | ||||
|         if (SDL.SDL_GL_MakeCurrent(window, ctx) < 0) panic(); | ||||
|  | ||||
|         const texture = SDL.SDL_CreateTexture( | ||||
|             renderer, | ||||
|             SDL.SDL_PIXELFORMAT_RGBA8888, | ||||
|             SDL.SDL_TEXTUREACCESS_STREAMING, | ||||
|             @as(c_int, width), | ||||
|             @as(c_int, height), | ||||
|         ) orelse panic(); | ||||
|         gl.load(ctx, Self.glGetProcAddress) catch @panic("gl.load failed"); | ||||
|         if (config.config().host.vsync) if (SDL.SDL_GL_SetSwapInterval(1) < 0) panic(); | ||||
|  | ||||
|         const program_id = compileShaders(); | ||||
|  | ||||
|         return Self{ | ||||
|             .window = window, | ||||
|             .title = span(title), | ||||
|             .renderer = renderer, | ||||
|             .texture = texture, | ||||
|             .ctx = ctx, | ||||
|             .program_id = program_id, | ||||
|             .audio = Audio.init(apu), | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     fn compileShaders() gl.GLuint { | ||||
|         // TODO: Panic on Shader Compiler Failure + Error Message | ||||
|         const vert_shader = @embedFile("shader/pixelbuf.vert"); | ||||
|         const frag_shader = @embedFile("shader/pixelbuf.frag"); | ||||
|  | ||||
|         const vs = gl.createShader(gl.VERTEX_SHADER); | ||||
|         defer gl.deleteShader(vs); | ||||
|  | ||||
|         gl.shaderSource(vs, 1, &[_][*c]const u8{vert_shader}, 0); | ||||
|         gl.compileShader(vs); | ||||
|  | ||||
|         const fs = gl.createShader(gl.FRAGMENT_SHADER); | ||||
|         defer gl.deleteShader(fs); | ||||
|  | ||||
|         gl.shaderSource(fs, 1, &[_][*c]const u8{frag_shader}, 0); | ||||
|         gl.compileShader(fs); | ||||
|  | ||||
|         const program = gl.createProgram(); | ||||
|         gl.attachShader(program, vs); | ||||
|         gl.attachShader(program, fs); | ||||
|         gl.linkProgram(program); | ||||
|  | ||||
|         return program; | ||||
|     } | ||||
|  | ||||
|     // Returns the VAO ID since it's used in run() | ||||
|     fn generateBuffers() [3]c_uint { | ||||
|         var vao_id: c_uint = undefined; | ||||
|         var vbo_id: c_uint = undefined; | ||||
|         var ebo_id: c_uint = undefined; | ||||
|         gl.genVertexArrays(1, &vao_id); | ||||
|         gl.genBuffers(1, &vbo_id); | ||||
|         gl.genBuffers(1, &ebo_id); | ||||
|  | ||||
|         gl.bindVertexArray(vao_id); | ||||
|  | ||||
|         gl.bindBuffer(gl.ARRAY_BUFFER, vbo_id); | ||||
|         gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(@TypeOf(vertices)), &vertices, gl.STATIC_DRAW); | ||||
|  | ||||
|         gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo_id); | ||||
|         gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, @sizeOf(@TypeOf(indices)), &indices, gl.STATIC_DRAW); | ||||
|  | ||||
|         // Position | ||||
|         gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), &0); | ||||
|         gl.enableVertexAttribArray(0); | ||||
|         // Colour | ||||
|         gl.vertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), &(3 * @sizeOf(f32))); | ||||
|         gl.enableVertexAttribArray(1); | ||||
|         // Texture Coord | ||||
|         gl.vertexAttribPointer(2, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), &(6 * @sizeOf(f32))); | ||||
|         gl.enableVertexAttribArray(2); | ||||
|  | ||||
|         return .{ vao_id, vbo_id, ebo_id }; | ||||
|     } | ||||
|  | ||||
|     fn generateTexture(buf: []const u8) c_uint { | ||||
|         var tex_id: c_uint = undefined; | ||||
|         gl.genTextures(1, &tex_id); | ||||
|         gl.bindTexture(gl.TEXTURE_2D, tex_id); | ||||
|  | ||||
|         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | ||||
|         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | ||||
|  | ||||
|         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | ||||
|         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | ||||
|  | ||||
|         gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gba_width, gba_height, 0, gl.RGBA, gl.UNSIGNED_BYTE, buf.ptr); | ||||
|         // gl.generateMipmap(gl.TEXTURE_2D); // TODO: Remove? | ||||
|  | ||||
|         return tex_id; | ||||
|     } | ||||
|  | ||||
|     pub fn run(self: *Self, cpu: *Arm7tdmi, scheduler: *Scheduler) !void { | ||||
|         var quit = std.atomic.Atomic(bool).init(false); | ||||
|         var tracker = FpsTracker.init(); | ||||
| @@ -67,6 +159,9 @@ pub const Gui = struct { | ||||
|  | ||||
|         var title_buf: [0x100]u8 = [_]u8{0} ** 0x100; | ||||
|  | ||||
|         const vao_id = Self.generateBuffers()[0]; | ||||
|         _ = Self.generateTexture(cpu.bus.ppu.framebuf.get(.Renderer)); | ||||
|  | ||||
|         emu_loop: while (true) { | ||||
|             var event: SDL.SDL_Event = undefined; | ||||
|             while (SDL.SDL_PollEvent(&event) != 0) { | ||||
| @@ -125,9 +220,12 @@ pub const Gui = struct { | ||||
|  | ||||
|             // Emulator has an internal Double Buffer | ||||
|             const framebuf = cpu.bus.ppu.framebuf.get(.Renderer); | ||||
|             _ = SDL.SDL_UpdateTexture(self.texture, null, framebuf.ptr, pitch); | ||||
|             _ = SDL.SDL_RenderCopy(self.renderer, self.texture, null, null); | ||||
|             SDL.SDL_RenderPresent(self.renderer); | ||||
|             gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gba_width, gba_height, gl.RGBA, gl.UNSIGNED_BYTE, framebuf.ptr); | ||||
|  | ||||
|             gl.useProgram(self.program_id); | ||||
|             gl.bindVertexArray(vao_id); | ||||
|             gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null); | ||||
|             SDL.SDL_GL_SwapWindow(self.window); | ||||
|  | ||||
|             const dyn_title = std.fmt.bufPrint(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.title, tracker.value() }) catch unreachable; | ||||
|             SDL.SDL_SetWindowTitle(self.window, dyn_title.ptr); | ||||
| @@ -138,12 +236,17 @@ pub const Gui = struct { | ||||
|  | ||||
|     pub fn deinit(self: *Self) void { | ||||
|         self.audio.deinit(); | ||||
|         SDL.SDL_DestroyTexture(self.texture); | ||||
|         SDL.SDL_DestroyRenderer(self.renderer); | ||||
|  | ||||
|         SDL.SDL_GL_DeleteContext(self.ctx); | ||||
|         SDL.SDL_DestroyWindow(self.window); | ||||
|         SDL.SDL_Quit(); | ||||
|         self.* = undefined; | ||||
|     } | ||||
|  | ||||
|     fn glGetProcAddress(ctx: SDL.SDL_GLContext, proc: [:0]const u8) ?*anyopaque { | ||||
|         _ = ctx; | ||||
|         return SDL.SDL_GL_GetProcAddress(@ptrCast([*c]const u8, proc)); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const Audio = struct { | ||||
|   | ||||
							
								
								
									
										14
									
								
								src/shader/pixelbuf.frag
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/shader/pixelbuf.frag
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #version 450 core | ||||
| out vec4 FragColor; | ||||
|  | ||||
| in vec3 ourColor; | ||||
| in vec2 TexCoord; | ||||
|  | ||||
| // texture sampler | ||||
| uniform sampler2D texture1; | ||||
|  | ||||
| void main() | ||||
| { | ||||
| 	FragColor = texture(texture1, TexCoord).wzyx; | ||||
| } | ||||
|  | ||||
							
								
								
									
										14
									
								
								src/shader/pixelbuf.vert
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/shader/pixelbuf.vert
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #version 450 core | ||||
| layout (location = 0) in vec3 aPos; | ||||
| layout (location = 1) in vec3 aColor; | ||||
| layout (location = 2) in vec2 aTexCoord; | ||||
|  | ||||
| out vec3 ourColor; | ||||
| out vec2 TexCoord; | ||||
|  | ||||
| void main() | ||||
| { | ||||
| 	gl_Position = vec4(aPos, 1.0); | ||||
| 	ourColor = aColor; | ||||
| 	TexCoord = vec2(aTexCoord.x, aTexCoord.y); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user