Add compute shader support

This commit is contained in:
sergeypdev 2024-09-10 13:05:27 +04:00
parent 743e3293bd
commit d708144dca
13 changed files with 101 additions and 59 deletions

View File

@ -2,5 +2,6 @@
{ {
"shader": "bloom_downsample.glsl", "shader": "bloom_downsample.glsl",
"vertex": true, "vertex": true,
"fragment": true "fragment": true,
"compute": false
} }

View File

@ -1,6 +1,6 @@
{ {
"shader": "bloom_upsample.glsl", "shader": "bloom_upsample.glsl",
"vertex": true, "vertex": true,
"fragment": true "fragment": true,
"compute": false
} }

View File

@ -2,5 +2,6 @@
{ {
"shader": "cube_shadow.glsl", "shader": "cube_shadow.glsl",
"vertex": true, "vertex": true,
"fragment": true "fragment": true,
"compute": false
} }

View File

@ -1,5 +1,6 @@
{ {
"shader": "debug.glsl", "shader": "debug.glsl",
"vertex": true, "vertex": true,
"fragment": true "fragment": true,
"compute": false
} }

View File

@ -1,5 +1,6 @@
{ {
"shader": "mesh.glsl", "shader": "mesh.glsl",
"vertex": true, "vertex": true,
"fragment": true "fragment": true,
"compute": false
} }

View File

@ -2,5 +2,6 @@
{ {
"shader": "post_process.glsl", "shader": "post_process.glsl",
"vertex": true, "vertex": true,
"fragment": true "fragment": true,
"compute": false
} }

View File

@ -2,5 +2,6 @@
{ {
"shader": "shadow.glsl", "shader": "shadow.glsl",
"vertex": true, "vertex": true,
"fragment": true "fragment": true,
"compute": false
} }

View File

@ -1,5 +1,6 @@
{ {
"shader": "unlit.glsl", "shader": "unlit.glsl",
"vertex": true, "vertex": true,
"fragment": true "fragment": true,
"compute": false
} }

View File

@ -2,5 +2,6 @@
{ {
"shader": "z_prepass.glsl", "shader": "z_prepass.glsl",
"vertex": true, "vertex": true,
"fragment": true "fragment": true,
"compute": false
} }

View File

@ -304,6 +304,7 @@ fn didUpdate(self: *AssetManager, path: []const u8, last_modified: i128) bool {
pub const ShaderProgramDefinition = struct { pub const ShaderProgramDefinition = struct {
vertex: []const u8, vertex: []const u8,
fragment: []const u8, fragment: []const u8,
compute: []const u8,
}; };
pub fn loadShaderProgram(self: *AssetManager, handle: Handle.ShaderProgram, permuted_id: AssetId, defines: []const DefinePair) LoadedShaderProgram { pub fn loadShaderProgram(self: *AssetManager, handle: Handle.ShaderProgram, permuted_id: AssetId, defines: []const DefinePair) LoadedShaderProgram {
@ -318,8 +319,11 @@ fn loadShaderProgramErr(self: *AssetManager, id: AssetId, permuted_id: AssetId,
const data = try self.loadFile(self.frame_arena, asset_manifest.getPath(id), SHADER_MAX_BYTES); const data = try self.loadFile(self.frame_arena, asset_manifest.getPath(id), SHADER_MAX_BYTES);
const program = formats.ShaderProgram.fromBuffer(data.bytes); const program = formats.ShaderProgram.fromBuffer(data.bytes);
if (!program.flags.vertex or !program.flags.fragment) { const graphics_pipeline = program.flags.vertex and program.flags.fragment;
std.log.err("Can't compile shader program {s} without vertex AND fragment shaders\n", .{asset_manifest.getPath(id)}); const compute_pipeline = program.flags.compute;
if (!graphics_pipeline and !compute_pipeline) {
std.log.err("Can't compile shader program {s} without vertex AND fragment shaders or a compute shader\n", .{asset_manifest.getPath(id)});
return error.UnsupportedShader; return error.UnsupportedShader;
} }
@ -330,17 +334,27 @@ fn loadShaderProgramErr(self: *AssetManager, id: AssetId, permuted_id: AssetId,
const prog = gl.createProgram(); const prog = gl.createProgram();
errdefer gl.deleteProgram(prog); errdefer gl.deleteProgram(prog);
const vertex_shader = try self.compileShader(shader.source, .vertex); if (program.flags.vertex and program.flags.fragment) {
defer gl.deleteShader(vertex_shader); const vertex_shader = try self.compileShader(shader.source, .vertex);
const fragment_shader = try self.compileShader(shader.source, .fragment); defer gl.deleteShader(vertex_shader);
defer gl.deleteShader(fragment_shader); const fragment_shader = try self.compileShader(shader.source, .fragment);
defer gl.deleteShader(fragment_shader);
gl.attachShader(prog, vertex_shader); gl.attachShader(prog, vertex_shader);
defer gl.detachShader(prog, vertex_shader); defer gl.detachShader(prog, vertex_shader);
gl.attachShader(prog, fragment_shader); gl.attachShader(prog, fragment_shader);
defer gl.detachShader(prog, fragment_shader); defer gl.detachShader(prog, fragment_shader);
gl.linkProgram(prog); gl.linkProgram(prog);
} else {
const compute_shader = try self.compileShader(shader.source, .compute);
defer gl.deleteShader(compute_shader);
gl.attachShader(prog, compute_shader);
defer gl.detachShader(prog, compute_shader);
gl.linkProgram(prog);
}
var success: c_int = 0; var success: c_int = 0;
gl.getProgramiv(prog, gl.LINK_STATUS, &success); gl.getProgramiv(prog, gl.LINK_STATUS, &success);
@ -774,20 +788,24 @@ pub const IndexSlice = struct {
pub const ShaderType = enum { pub const ShaderType = enum {
vertex, vertex,
fragment, fragment,
compute,
pub fn goGLType(self: ShaderType) gl.GLenum { pub fn goGLType(self: ShaderType) gl.GLenum {
return switch (self) { return switch (self) {
.vertex => gl.VERTEX_SHADER, .vertex => gl.VERTEX_SHADER,
.fragment => gl.FRAGMENT_SHADER, .fragment => gl.FRAGMENT_SHADER,
.compute => gl.COMPUTE_SHADER,
}; };
} }
const VERTEX_DEFINES = "#version 460 core\n#define VERTEX_SHADER 1\n#define VERTEX_EXPORT out\n"; const VERTEX_DEFINES = "#version 460 core\n#define VERTEX_SHADER 1\n#define VERTEX_EXPORT out\n";
const FRAGMENT_DEFINES = "#version 460 core\n#define FRAGMENT_SHADER 1\n#define VERTEX_EXPORT in\n"; const FRAGMENT_DEFINES = "#version 460 core\n#define FRAGMENT_SHADER 1\n#define VERTEX_EXPORT in\n";
const COMPUTE_DEFINES = "#version 460 core\n#define COMPUTE_SHADER 1\n";
pub fn getDefines(self: ShaderType) []const u8 { pub fn getDefines(self: ShaderType) []const u8 {
return switch (self) { return switch (self) {
.vertex => VERTEX_DEFINES, .vertex => VERTEX_DEFINES,
.fragment => FRAGMENT_DEFINES, .fragment => FRAGMENT_DEFINES,
.compute => COMPUTE_DEFINES,
}; };
} }
}; };

View File

@ -294,15 +294,16 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm
gl.createFramebuffers(1, &render.shadow_framebuffer); gl.createFramebuffers(1, &render.shadow_framebuffer);
checkGLError(); checkGLError();
std.debug.assert(render.shadow_framebuffer != 0); std.debug.assert(render.shadow_framebuffer != 0);
gl.namedFramebufferDrawBuffer(render.shadow_framebuffer, gl.FRONT_LEFT);
gl.namedFramebufferReadBuffer(render.shadow_framebuffer, gl.NONE);
} }
// Verify directional shadow framebuffer setup // Verify directional shadow framebuffer setup
{ {
gl.namedFramebufferTextureLayer(render.shadow_framebuffer, gl.COLOR_ATTACHMENT0, render.shadow_texture_array, 0, 0); gl.namedFramebufferTextureLayer(render.shadow_framebuffer, gl.COLOR_ATTACHMENT0, render.shadow_texture_array, 0, 0);
checkGLError();
gl.namedFramebufferTexture(render.shadow_framebuffer, gl.DEPTH_ATTACHMENT, render.direct_shadow_depth_buffer_texture, 0); gl.namedFramebufferTexture(render.shadow_framebuffer, gl.DEPTH_ATTACHMENT, render.direct_shadow_depth_buffer_texture, 0);
checkGLError();
const check_fbo_status = gl.checkNamedFramebufferStatus(render.shadow_framebuffer, gl.DRAW_FRAMEBUFFER); const check_fbo_status = gl.checkNamedFramebufferStatus(render.shadow_framebuffer, gl.DRAW_FRAMEBUFFER);
checkGLError();
if (check_fbo_status != gl.FRAMEBUFFER_COMPLETE) { if (check_fbo_status != gl.FRAMEBUFFER_COMPLETE) {
std.log.debug("Shadow Framebuffer Incomplete: {}\n", .{check_fbo_status}); std.log.debug("Shadow Framebuffer Incomplete: {}\n", .{check_fbo_status});
} }
@ -311,14 +312,18 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm
// Verify cube shadow framebuffer setup // Verify cube shadow framebuffer setup
{ {
gl.namedFramebufferTextureLayer(render.shadow_framebuffer, gl.COLOR_ATTACHMENT0, render.cube_shadow_texture_array, 0, 0); gl.namedFramebufferTextureLayer(render.shadow_framebuffer, gl.COLOR_ATTACHMENT0, render.cube_shadow_texture_array, 0, 0);
checkGLError();
gl.namedFramebufferTexture(render.shadow_framebuffer, gl.DEPTH_ATTACHMENT, render.direct_shadow_depth_buffer_texture, 0); gl.namedFramebufferTexture(render.shadow_framebuffer, gl.DEPTH_ATTACHMENT, render.direct_shadow_depth_buffer_texture, 0);
checkGLError();
const check_fbo_status = gl.checkNamedFramebufferStatus(render.shadow_framebuffer, gl.DRAW_FRAMEBUFFER); const check_fbo_status = gl.checkNamedFramebufferStatus(render.shadow_framebuffer, gl.DRAW_FRAMEBUFFER);
checkGLError();
if (check_fbo_status != gl.FRAMEBUFFER_COMPLETE) { if (check_fbo_status != gl.FRAMEBUFFER_COMPLETE) {
std.log.debug("Shadow Framebuffer Incomplete: {}\n", .{check_fbo_status}); std.log.debug("Shadow Framebuffer Incomplete: {}\n", .{check_fbo_status});
} }
} }
gl.createBuffers(1, &render.shadow_matrices_buffer); gl.createBuffers(1, &render.shadow_matrices_buffer);
checkGLError();
gl.namedBufferStorage( gl.namedBufferStorage(
render.shadow_matrices_buffer, render.shadow_matrices_buffer,
@ -326,6 +331,7 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm
null, null,
gl.DYNAMIC_STORAGE_BIT, gl.DYNAMIC_STORAGE_BIT,
); );
checkGLError();
// SHADOW VAO // SHADOW VAO
var vao: gl.GLuint = 0; var vao: gl.GLuint = 0;
@ -338,35 +344,19 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm
gl.enableVertexArrayAttrib(vao, Attrib.Position.value()); gl.enableVertexArrayAttrib(vao, Attrib.Position.value());
gl.vertexArrayAttribBinding(vao, Attrib.Position.value(), 0); gl.vertexArrayAttribBinding(vao, Attrib.Position.value(), 0);
gl.vertexArrayAttribFormat(vao, Attrib.Position.value(), 3, gl.FLOAT, gl.FALSE, 0); gl.vertexArrayAttribFormat(vao, Attrib.Position.value(), 3, gl.FLOAT, gl.FALSE, 0);
checkGLError();
} }
// Screen HDR FBO // Screen HDR FBO
{ {
gl.createFramebuffers(1, &render.screen_fbo); gl.createFramebuffers(1, &render.screen_fbo);
checkGLError();
std.debug.assert(render.screen_fbo != 0); std.debug.assert(render.screen_fbo != 0);
var width: c_int = 0; var width: c_int = 0;
var height: c_int = 0; var height: c_int = 0;
c.SDL_GL_GetDrawableSize(globals.g_init.window, &width, &height); c.SDL_GL_GetDrawableSize(globals.g_init.window, &width, &height);
var textures = [2]gl.GLuint{ 0, 0 };
gl.createTextures(gl.TEXTURE_2D, textures.len, &textures);
render.screen_color_texture = textures[0];
render.screen_depth_texture = textures[1];
std.debug.assert(render.screen_color_texture != 0);
std.debug.assert(render.screen_depth_texture != 0);
gl.textureParameteri(render.screen_color_texture, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.textureParameteri(render.screen_color_texture, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.textureParameteri(render.screen_color_texture, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.textureParameteri(render.screen_color_texture, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.textureParameteri(render.screen_depth_texture, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.textureParameteri(render.screen_depth_texture, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.textureParameteri(render.screen_depth_texture, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.textureParameteri(render.screen_depth_texture, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
render.updateScreenBufferSize(width, height); render.updateScreenBufferSize(width, height);
} }
@ -425,21 +415,44 @@ fn calculateMipCount(width: c_int, height: c_int) usize {
} }
fn updateScreenBufferSize(self: *Render, width: c_int, height: c_int) void { fn updateScreenBufferSize(self: *Render, width: c_int, height: c_int) void {
const mip_count = calculateMipCount(width, height); if (self.screen_tex_size.eql(Vec2_i32.new(width, height)) and self.screen_color_texture != 0) {
return;
gl.bindTexture(gl.TEXTURE_2D, self.screen_color_texture);
for (0..mip_count) |mip_level| {
const size = getMipSize(width, height, mip_level);
std.log.debug("screen_color mip {} size {}x{}\n", .{ mip_level, size.x(), size.y() });
gl.texImage2D(gl.TEXTURE_2D, @intCast(mip_level), gl.RGB16F, size.x(), size.y(), 0, gl.RGB, gl.HALF_FLOAT, null);
checkGLError();
} }
// Depth doesn't need any mips cause it's not filterable anyway if (self.screen_color_texture != 0) {
gl.bindTexture(gl.TEXTURE_2D, self.screen_depth_texture); const old_textures = [_]gl.GLuint{ self.screen_color_texture, self.screen_depth_texture };
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT32F, width, height, 0, gl.DEPTH_COMPONENT, gl.FLOAT, null); gl.deleteTextures(old_textures.len, &old_textures);
checkGLError();
self.screen_color_texture = 0;
self.screen_depth_texture = 0;
}
var textures = [2]gl.GLuint{ 0, 0 };
gl.createTextures(gl.TEXTURE_2D, textures.len, &textures);
checkGLError(); checkGLError();
self.screen_color_texture = textures[0];
self.screen_depth_texture = textures[1];
std.debug.assert(self.screen_color_texture != 0);
std.debug.assert(self.screen_depth_texture != 0);
const mip_count = calculateMipCount(width, height);
std.debug.print("screen_color_tex {}, depth {}, screen size {}x{}, mip count: {}\n", .{ self.screen_color_texture, self.screen_depth_texture, width, height, mip_count });
gl.textureStorage2D(self.screen_color_texture, @intCast(mip_count), gl.RGBA16F, width, height);
checkGLError();
gl.textureStorage2D(self.screen_depth_texture, 1, gl.DEPTH_COMPONENT24, width, height);
checkGLError();
gl.textureParameteri(self.screen_color_texture, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.textureParameteri(self.screen_color_texture, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.textureParameteri(self.screen_color_texture, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.textureParameteri(self.screen_color_texture, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.textureParameteri(self.screen_depth_texture, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.textureParameteri(self.screen_depth_texture, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.textureParameteri(self.screen_depth_texture, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.textureParameteri(self.screen_depth_texture, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
self.screen_tex_size = Vec2_i32.new(width, height); self.screen_tex_size = Vec2_i32.new(width, height);
self.screen_mip_count = mip_count; self.screen_mip_count = mip_count;
@ -1489,6 +1502,7 @@ pub fn checkGLError() void {
std.log.scoped(.OpenGL).err("OpenGL Failure: {s}\n", .{name}); std.log.scoped(.OpenGL).err("OpenGL Failure: {s}\n", .{name});
} }
@panic("GL Error");
} }
pub const DrawCommand = struct { pub const DrawCommand = struct {

View File

@ -124,7 +124,8 @@ pub const ShaderProgram = extern struct {
pub const Flags = packed struct { pub const Flags = packed struct {
vertex: bool, vertex: bool,
fragment: bool, fragment: bool,
_pad: u6 = 0, compute: bool,
_pad: u5 = 0,
}; };
comptime { comptime {
if (@bitSizeOf(Flags) != 8) { if (@bitSizeOf(Flags) != 8) {
@ -142,24 +143,24 @@ pub const ShaderProgram = extern struct {
test "ShaderProgram serialization" { test "ShaderProgram serialization" {
const source = ShaderProgram{ const source = ShaderProgram{
.flags = .{ .vertex = true, .fragment = true }, .flags = .{ .vertex = true, .fragment = true, .compute = true },
.shader = .{ .id = 123 }, .shader = .{ .id = 123 },
}; };
var buf: [@sizeOf(ShaderProgram)]u8 = undefined; var buf: [@sizeOf(ShaderProgram)]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf); var stream = std.io.fixedBufferStream(&buf);
try writeShaderProgram(stream.writer(), source.shader.id, source.flags.vertex, source.flags.fragment, native_endian); try writeShaderProgram(stream.writer(), source.shader.id, source.flags.vertex, source.flags.fragment, source.flags.compute, native_endian);
const result: *align(1) ShaderProgram = @ptrCast(&buf); const result: *align(1) ShaderProgram = @ptrCast(&buf);
try std.testing.expectEqual(source, result.*); try std.testing.expectEqual(source, result.*);
} }
pub fn writeShaderProgram(writer: anytype, shader: u64, vertex: bool, fragment: bool, endian: std.builtin.Endian) !void { pub fn writeShaderProgram(writer: anytype, shader: u64, vertex: bool, fragment: bool, compute: bool, endian: std.builtin.Endian) !void {
try writer.writeInt(u64, shader, endian); try writer.writeInt(u64, shader, endian);
try writer.writeInt( try writer.writeInt(
u8, u8,
@bitCast(ShaderProgram.Flags{ .vertex = vertex, .fragment = fragment }), @bitCast(ShaderProgram.Flags{ .vertex = vertex, .fragment = fragment, .compute = compute }),
endian, endian,
); );
} }

View File

@ -532,6 +532,7 @@ fn processShaderProgram(allocator: std.mem.Allocator, input: []const u8, output_
shader: []const u8, shader: []const u8,
vertex: bool, vertex: bool,
fragment: bool, fragment: bool,
compute: bool,
}; };
const program = try std.json.parseFromSlice(ShaderProgram, allocator, file_contents, .{}); const program = try std.json.parseFromSlice(ShaderProgram, allocator, file_contents, .{});
defer program.deinit(); defer program.deinit();
@ -549,7 +550,7 @@ fn processShaderProgram(allocator: std.mem.Allocator, input: []const u8, output_
defer output.file.close(); defer output.file.close();
var buf_writer = std.io.bufferedWriter(output.file.writer()); var buf_writer = std.io.bufferedWriter(output.file.writer());
try formats.writeShaderProgram(buf_writer.writer(), shader_asset_id, program.value.vertex, program.value.fragment, formats.native_endian); try formats.writeShaderProgram(buf_writer.writer(), shader_asset_id, program.value.vertex, program.value.fragment, program.value.compute, formats.native_endian);
try buf_writer.flush(); try buf_writer.flush();
} }
const MipLevel = struct { const MipLevel = struct {