Compare commits

...

8 Commits

Author SHA1 Message Date
duck
0658c2fdfa Optional antialiasing 2025-09-26 00:19:11 +05:00
duck
9d3a98b2d9 Draw call batching for improved performance 2025-09-21 21:30:02 +05:00
duck
49267e629f Hi-dpi support, fsaa tweaks 2025-09-21 02:38:49 +05:00
duck
640e7bed86 Fix some issues with asset unloading 2025-09-20 23:22:30 +05:00
duck
fc6ebb73b8 Use reversed infinite projection for camera 2025-09-20 19:03:36 +05:00
duck
c59d833542 Zig 0.14 => Zig 0.15, STB Image => SDL3 Image 2025-09-20 17:56:15 +05:00
duck
9933a956c2 Some .ignore file fixes 2025-09-20 01:36:26 +05:00
duck
f836ab8b20 Gltf parsing and rendering 2025-09-19 11:09:45 +05:00
28 changed files with 1476 additions and 8408 deletions

5
.ignore Normal file
View File

@@ -0,0 +1,5 @@
/data/**/*.png
/data/**/*.bin
/data/**/*.blend
/data/**/*.blend1
/.git

View File

@@ -6,6 +6,10 @@ const SHADERS: []const []const u8 = &.{
"data/shaders/basic.vert", "data/shaders/basic.vert",
"data/shaders/basic.frag", "data/shaders/basic.frag",
}; };
const SDL_LIBS: []const []const u8 = &.{
"SDL3",
"SDL3_image",
};
pub fn build(b: *Build) void { pub fn build(b: *Build) void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
@@ -56,16 +60,16 @@ fn stepBuildClient(
) *Build.Step.Compile { ) *Build.Step.Compile {
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "tabletop_client", .name = "tabletop_client",
.root_source_file = b.path("src/client.zig"), .root_module = b.createModule(.{
.target = target, .root_source_file = b.path("src/client.zig"),
.optimize = optimize, .target = target,
.optimize = optimize,
}),
}); });
exe.root_module.addImport("sdl", sdl_module); exe.root_module.addImport("sdl", sdl_module);
exe.step.dependOn(sdl_step); exe.step.dependOn(sdl_step);
exe.addIncludePath(b.path("lib/clibs"));
return exe; return exe;
} }
@@ -76,9 +80,11 @@ fn stepBuildServer(
) *Build.Step.Compile { ) *Build.Step.Compile {
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "tabletop_server", .name = "tabletop_server",
.root_source_file = b.path("src/server.zig"), .root_module = b.createModule(.{
.target = target, .root_source_file = b.path("src/server.zig"),
.optimize = optimize, .target = target,
.optimize = optimize,
}),
}); });
return exe; return exe;
@@ -93,16 +99,16 @@ fn stepBuildOffline(
) *Build.Step.Compile { ) *Build.Step.Compile {
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "tabletop", .name = "tabletop",
.root_source_file = b.path("src/offline.zig"), .root_module = b.createModule(.{
.target = target, .root_source_file = b.path("src/offline.zig"),
.optimize = optimize, .target = target,
.optimize = optimize,
}),
}); });
exe.root_module.addImport("sdl", sdl_module); exe.root_module.addImport("sdl", sdl_module);
exe.step.dependOn(sdl_step); exe.step.dependOn(sdl_step);
exe.addIncludePath(b.path("lib/clibs"));
return exe; return exe;
} }
@@ -112,11 +118,15 @@ fn stepBuildSdlTranslator(
) *Build.Step.Compile { ) *Build.Step.Compile {
const sdl_translator = b.addExecutable(.{ const sdl_translator = b.addExecutable(.{
.name = "sdl_header_translator", .name = "sdl_header_translator",
.root_source_file = b.path("utils/sdl_translator.zig"), .root_module = b.createModule(.{
.target = target, .root_source_file = b.path("utils/sdl_translator.zig"),
.optimize = .Debug, .target = target,
.optimize = .Debug,
}),
}); });
sdl_translator.linkSystemLibrary("SDL3"); for (SDL_LIBS) |lib| {
sdl_translator.linkSystemLibrary(lib);
}
return sdl_translator; return sdl_translator;
} }
@@ -138,20 +148,25 @@ fn stepSdlModule(
target: Build.ResolvedTarget, target: Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode, optimize: std.builtin.OptimizeMode,
) struct { *Build.Module, *Build.Step } { ) struct { *Build.Module, *Build.Step } {
const sdl_module = b.addModule("sdl", .{ const translate_sdl_step, const sdl_rename = stepTranslateSdl(b, target);
.root_source_file = b.path("lib/sdl.zig"),
const translate_c = b.addTranslateC(.{
.root_source_file = b.path("lib/sdl.h"),
.link_libc = true, .link_libc = true,
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
sdl_module.linkSystemLibrary("SDL3", .{}); translate_c.addIncludePath(sdl_rename.dirname());
const translate_module = translate_c.createModule();
for (SDL_LIBS) |lib| {
translate_module.linkSystemLibrary(lib, .{ .needed = true });
}
const translate_step, const sdl_rename = stepTranslateSdl(b, target); translate_c.step.dependOn(translate_sdl_step);
sdl_module.addIncludePath(sdl_rename.dirname());
return .{ return .{
sdl_module, translate_module,
translate_step, &translate_c.step,
}; };
} }

BIN
data/cubemap.bin Normal file

Binary file not shown.

142
data/cubemap.gltf Normal file
View File

@@ -0,0 +1,142 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.5.48",
"version":"2.0"
},
"extensionsUsed":[
"KHR_materials_unlit"
],
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"cubemap"
}
],
"materials":[
{
"extensions":{
"KHR_materials_unlit":{}
},
"name":"Material_0",
"pbrMetallicRoughness":{
"baseColorTexture":{
"index":0
},
"metallicFactor":0,
"roughnessFactor":0.9
}
}
],
"meshes":[
{
"name":"Cube.001",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"cubemap.png",
"uri":"cubemap.png"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
0.4999999701976776,
0.5,
0.5
],
"min":[
-0.4999999701976776,
-0.5,
-0.5
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":576,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":768,
"target":34963
}
],
"samplers":[
{
"magFilter":9729,
"minFilter":9987
}
],
"buffers":[
{
"byteLength":840,
"uri":"cubemap.bin"
}
]
}

BIN
data/hand.bin Normal file

Binary file not shown.

144
data/hand.gltf Normal file
View File

@@ -0,0 +1,144 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.5.48",
"version":"2.0"
},
"extensionsUsed":[
"KHR_materials_unlit"
],
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"hand"
}
],
"materials":[
{
"alphaMode":"BLEND",
"doubleSided":true,
"extensions":{
"KHR_materials_unlit":{}
},
"name":"Material",
"pbrMetallicRoughness":{
"baseColorTexture":{
"index":0
},
"metallicFactor":0,
"roughnessFactor":0.9
}
}
],
"meshes":[
{
"name":"Plane.001",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"hand.png",
"uri":"hand.png"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":4,
"max":[
0.5,
0.5,
0
],
"min":[
-0.5,
-0.5,
0
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":4,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":4,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":6,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":48,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":48,
"byteOffset":48,
"target":34962
},
{
"buffer":0,
"byteLength":32,
"byteOffset":96,
"target":34962
},
{
"buffer":0,
"byteLength":12,
"byteOffset":128,
"target":34963
}
],
"samplers":[
{
"magFilter":9728,
"minFilter":9984
}
],
"buffers":[
{
"byteLength":140,
"uri":"hand.bin"
}
]
}

View File

@@ -13,6 +13,6 @@ layout(set = 1, binding = 1) uniform Object{
void main() { void main() {
gl_Position = vec4(inCoord, 1.0) * object.transform * camera.transform; gl_Position = vec4(inCoord, 1.0) * object.transform * camera.transform;
gl_ClipDistance[0] = gl_Position.z; gl_ClipDistance[0] = gl_Position.w - gl_Position.z;
outUV = inUV; outUV = inUV;
} }

BIN
data/yakuza/card.bin Normal file

Binary file not shown.

144
data/yakuza/card.gltf Normal file
View File

@@ -0,0 +1,144 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.5.48",
"version":"2.0"
},
"extensionsUsed":[
"KHR_materials_unlit"
],
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"card"
}
],
"materials":[
{
"alphaMode":"BLEND",
"doubleSided":true,
"extensions":{
"KHR_materials_unlit":{}
},
"name":"Material",
"pbrMetallicRoughness":{
"baseColorTexture":{
"index":0
},
"metallicFactor":0,
"roughnessFactor":0.9
}
}
],
"meshes":[
{
"name":"Plane.002",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"game.png",
"uri":"game.png"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":120,
"max":[
0.25,
0.25,
0.00390625
],
"min":[
-0.25,
-0.25,
-0.00390625
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":120,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":120,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":228,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":1440,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":1440,
"byteOffset":1440,
"target":34962
},
{
"buffer":0,
"byteLength":960,
"byteOffset":2880,
"target":34962
},
{
"buffer":0,
"byteLength":456,
"byteOffset":3840,
"target":34963
}
],
"samplers":[
{
"magFilter":9728,
"minFilter":9984
}
],
"buffers":[
{
"byteLength":4296,
"uri":"card.bin"
}
]
}

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

BIN
data/yakuza/pad.bin Normal file

Binary file not shown.

144
data/yakuza/pad.gltf Normal file
View File

@@ -0,0 +1,144 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.5.48",
"version":"2.0"
},
"extensionsUsed":[
"KHR_materials_unlit"
],
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"pad"
}
],
"materials":[
{
"alphaMode":"BLEND",
"doubleSided":true,
"extensions":{
"KHR_materials_unlit":{}
},
"name":"Material",
"pbrMetallicRoughness":{
"baseColorTexture":{
"index":0
},
"metallicFactor":0,
"roughnessFactor":0.9
}
}
],
"meshes":[
{
"name":"Plane.003",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"game.png",
"uri":"game.png"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":1272,
"max":[
0.4140625,
0.4140625,
0.00390625
],
"min":[
-0.4140625,
-0.4140625,
-0.00390625
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":1272,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":1272,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":2532,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":15264,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":15264,
"byteOffset":15264,
"target":34962
},
{
"buffer":0,
"byteLength":10176,
"byteOffset":30528,
"target":34962
},
{
"buffer":0,
"byteLength":5064,
"byteOffset":40704,
"target":34963
}
],
"samplers":[
{
"magFilter":9728,
"minFilter":9984
}
],
"buffers":[
{
"byteLength":45768,
"uri":"pad.bin"
}
]
}

BIN
data/yakuza/table.bin Normal file

Binary file not shown.

144
data/yakuza/table.gltf Normal file
View File

@@ -0,0 +1,144 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.5.48",
"version":"2.0"
},
"extensionsUsed":[
"KHR_materials_unlit"
],
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"table"
}
],
"materials":[
{
"alphaMode":"BLEND",
"doubleSided":true,
"extensions":{
"KHR_materials_unlit":{}
},
"name":"Material",
"pbrMetallicRoughness":{
"baseColorTexture":{
"index":0
},
"metallicFactor":0,
"roughnessFactor":0.9
}
}
],
"meshes":[
{
"name":"Plane",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"game.png",
"uri":"game.png"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
4,
4,
0
],
"min":[
-4,
-4,
-0.5
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":576,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":768,
"target":34963
}
],
"samplers":[
{
"magFilter":9728,
"minFilter":9984
}
],
"buffers":[
{
"byteLength":840,
"uri":"table.bin"
}
]
}

File diff suppressed because it is too large Load Diff

3
lib/sdl.h Normal file
View File

@@ -0,0 +1,3 @@
#include "SDL3/SDL.h"
#include "SDL3_image/SDL_image.h"
#include "sdl_rename.h"

View File

@@ -1,4 +0,0 @@
pub usingnamespace @cImport({
@cInclude("SDL3/SDL.h");
@cInclude("sdl_rename.h");
});

View File

@@ -1,11 +1,17 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const err = @import("error.zig"); const err = @import("error.zig");
const Game = @import("game.zig"); const Game = @import("game.zig");
const FileLoader = @import("assets/file.zig"); const FileLoader = @import("assets/file.zig");
const TextureLoader = @import("assets/texture.zig"); const TextureLoader = @import("assets/texture.zig");
const GltfLoader = @import("assets/gltf.zig");
const Assets = @This(); const Assets = @This();
// TODO: Unload assets in correct order to account for asset dependencies
pub const File = AssetContainer(FileLoader);
pub const Texture = AssetContainer(TextureLoader); pub const Texture = AssetContainer(TextureLoader);
pub const Object = AssetContainer(GltfLoader);
const WORKERS_MAX = 4; const WORKERS_MAX = 4;
var next_worker_update: usize = 0; var next_worker_update: usize = 0;
@@ -50,16 +56,19 @@ pub const LoadError = error{
ParsingError, ParsingError,
SdlError, SdlError,
FileTooBig, FileTooBig,
} || std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError; UnsupportedAsset,
} || std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || std.json.ParseError(std.json.Scanner);
pub const AssetType = enum { pub const AssetType = enum {
file, file,
texture, texture,
gltf,
pub fn getType(comptime self: @This()) type { pub fn getType(comptime self: @This()) type {
return switch (self) { return switch (self) {
.file => FileLoader, .file => FileLoader,
.texture => TextureLoader, .texture => TextureLoader,
.gltf => GltfLoader,
}; };
} }
}; };
@@ -82,6 +91,8 @@ pub const AssetCell = struct {
fn load(self: *AssetCell, alloc: std.mem.Allocator) void { fn load(self: *AssetCell, alloc: std.mem.Allocator) void {
self.loader(self, alloc) catch |e| { self.loader(self, alloc) catch |e| {
if (builtin.mode == .Debug)
std.debug.panic("Asset loading error: {s} - {}!\n", .{ self.path, e });
self.state = .{ .fail = e }; self.state = .{ .fail = e };
return; return;
}; };
@@ -113,12 +124,14 @@ pub fn AssetContainer(comptime T: type) type {
self.last_state = self.asset_pointer.state; self.last_state = self.asset_pointer.state;
} }
if (self.last_state == .loaded) { if (self.last_state == .loaded) {
self.data_pointer = @alignCast(@ptrCast(self.asset_pointer.data)); self.data_pointer = @ptrCast(@alignCast(self.asset_pointer.data));
return self.data_pointer; return self.data_pointer;
} else return null; } else return null;
}, },
} }
} }
// TODO: Add smth like `Assets.immediateLoad`
/// To be used by worker threads to request other assets /// To be used by worker threads to request other assets
pub fn getSync(self: *@This()) !*T { pub fn getSync(self: *@This()) !*T {
sw: switch (self.last_state) { sw: switch (self.last_state) {
@@ -138,7 +151,7 @@ pub fn AssetContainer(comptime T: type) type {
self.last_state = self.asset_pointer.state; self.last_state = self.asset_pointer.state;
} }
if (self.last_state == .loaded) { if (self.last_state == .loaded) {
self.data_pointer = @alignCast(@ptrCast(self.asset_pointer.data)); self.data_pointer = @ptrCast(@alignCast(self.asset_pointer.data));
} }
continue :sw self.last_state; continue :sw self.last_state;
}, },
@@ -163,11 +176,13 @@ pub fn deinit() void {
if (worker.thread == null) continue; if (worker.thread == null) continue;
worker.thread.?.join(); worker.thread.?.join();
} }
updateFree();
var iter = Assets.asset_map.valueIterator(); var iter = Assets.asset_map.valueIterator();
while (iter.next()) |asset| { while (iter.next()) |asset| {
std.debug.assert(asset.*.counter == 0); std.debug.assert(asset.*.counter == 0);
if (asset.*.state == .loaded) if (asset.*.state == .loaded)
asset.*.unload(Game.alloc); asset.*.unload(Game.alloc);
Game.alloc.free(asset.*.path);
Game.alloc.destroy(asset.*); Game.alloc.destroy(asset.*);
} }
Assets.asset_map.clearAndFree(Game.alloc); Assets.asset_map.clearAndFree(Game.alloc);
@@ -190,21 +205,28 @@ pub fn update() void {
Assets.next_worker_update = 0; Assets.next_worker_update = 0;
} }
Assets.free_board_mutex.lock(); updateFree();
defer Assets.free_board_mutex.unlock(); }
if (Assets.free_board.items.len == 0) return;
fn updateFree() void {
// TODO: Delegate freeing to worker threads? // TODO: Delegate freeing to worker threads?
Assets.asset_map_mutex.lock(); Assets.asset_map_mutex.lock();
defer Assets.asset_map_mutex.unlock(); defer Assets.asset_map_mutex.unlock();
Assets.free_board_mutex.lock();
while (Assets.free_board.pop()) |request| { while (Assets.free_board.pop()) |request| {
if (@atomicLoad(usize, &request.counter, .monotonic) == 0) { if (@atomicLoad(usize, &request.counter, .monotonic) == 0) {
if (!Assets.asset_map.remove(.{ .type = request.type, .path = request.path })) continue; if (!Assets.asset_map.remove(.{ .type = request.type, .path = request.path })) continue;
if (request.state == .loaded) if (request.state == .loaded) {
Assets.free_board_mutex.unlock();
request.unload(Game.alloc); request.unload(Game.alloc);
Assets.free_board_mutex.lock();
}
Game.alloc.free(request.path);
Game.alloc.destroy(request); Game.alloc.destroy(request);
} }
} }
Assets.free_board_mutex.unlock();
} }
pub fn load(comptime asset_type: AssetType, path: []const u8) AssetContainer(asset_type.getType()) { pub fn load(comptime asset_type: AssetType, path: []const u8) AssetContainer(asset_type.getType()) {
@@ -259,7 +281,7 @@ fn mapAsset(comptime asset_type: AssetType, path: []const u8) *AssetCell {
.mutex = .{}, .mutex = .{},
.type = asset_type, .type = asset_type,
.data = undefined, .data = undefined,
.path = path, .path = Game.alloc.dupe(u8, path) catch err.oom(),
.loader = Assets.makeLoader(asset_type.getType(), asset_type.getType().load), .loader = Assets.makeLoader(asset_type.getType(), asset_type.getType().load),
.unloader = Assets.makeUnloader(asset_type.getType(), asset_type.getType().unload), .unloader = Assets.makeUnloader(asset_type.getType(), asset_type.getType().unload),
.state = .not_loaded, .state = .not_loaded,
@@ -284,8 +306,8 @@ fn makeLoader(comptime T: type, comptime func: *const fn ([]const u8, std.mem.Al
fn makeUnloader(comptime T: type, comptime func: *const fn (T, std.mem.Allocator) void) *const fn (*AssetCell, std.mem.Allocator) void { fn makeUnloader(comptime T: type, comptime func: *const fn (T, std.mem.Allocator) void) *const fn (*AssetCell, std.mem.Allocator) void {
const Container = struct { const Container = struct {
pub fn unloader(cell: *AssetCell, alloc: std.mem.Allocator) void { pub fn unloader(cell: *AssetCell, alloc: std.mem.Allocator) void {
func(@as(*T, @alignCast(@ptrCast(cell.data))).*, alloc); func(@as(*T, @ptrCast(@alignCast(cell.data))).*, alloc);
alloc.destroy(@as(*T, @alignCast(@ptrCast(cell.data)))); alloc.destroy(@as(*T, @ptrCast(@alignCast(cell.data))));
} }
}; };
return Container.unloader; return Container.unloader;

389
src/assets/gltf.zig Normal file
View File

@@ -0,0 +1,389 @@
const std = @import("std");
const sdl = @import("sdl");
const err = @import("../error.zig");
const Assets = @import("../assets.zig");
const Graphics = @import("../graphics.zig");
nodes: []Node,
meshes: []Mesh,
const Node = struct {
mesh: u32,
};
const Mesh = struct {
primitives: []Primitive,
};
const Primitive = struct {
vertex_buffer: *sdl.GPUBuffer,
index_buffer: ?*sdl.GPUBuffer = null,
vertices: u32,
indices: u32 = 0,
texture: Assets.Texture,
};
const GltfJson = struct {
scene: u32,
scenes: []GltfSceneJson,
nodes: []GltfNodeJson,
materials: []GltfMaterialJson,
meshes: []GltfMeshJson,
textures: []GltfTextureJson,
images: []GltfImageJson,
accessors: []GltfAccessorJson,
bufferViews: []GltfBufferViewJson,
samplers: []GltfSamplerJson,
buffers: []GltfBufferJson,
const GltfSceneJson = struct {
nodes: []u32,
};
const GltfNodeJson = struct {
mesh: u32,
};
const GltfMaterialJson = struct {
pbrMetallicRoughness: GltfPbrMRJson,
const GltfPbrMRJson = struct {
baseColorTexture: GltfPbrMRBaseColorTexture,
const GltfPbrMRBaseColorTexture = struct {
index: u32,
};
};
};
const GltfMeshJson = struct {
primitives: []GltfPrimitiveJson,
const GltfPrimitiveJson = struct {
attributes: GltfAttributesJson,
indices: u32,
material: u32,
const GltfAttributesJson = struct {
POSITION: u32,
TEXCOORD_0: u32,
};
};
};
const GltfTextureJson = struct {
sampler: ?u32,
source: u32,
};
const GltfImageJson = struct {
uri: []u8,
};
const GltfAccessorJson = struct {
bufferView: u32,
componentType: GltfComponentTypeJson,
count: u32,
type: GltfAccessorTypeJson,
const GltfComponentTypeJson = enum(u32) {
i8 = 5120,
u8 = 5121,
i16 = 5122,
u16 = 5123,
i32 = 5125,
f32 = 5126,
};
const GltfAccessorTypeJson = enum {
SCALAR,
VEC2,
VEC3,
VEC4,
MAT2,
MAT3,
MAT4,
};
pub fn slice(accessor: @This(), comptime T: type, views: []const GltfBufferViewJson, buffers: []Assets.File) !?[]align(1) T {
if (accessor.bufferView >= views.len) return null;
const view = &views[accessor.bufferView];
if (view.buffer >= buffers.len) return null;
const buffer = try buffers[view.buffer].getSync();
const component_length = switch (T) {
[3]f32 => 12,
[2]f32 => 8,
u16 => 2,
else => @compileError("Accessor of " ++ @tagName(accessor.type) ++ " of " ++ @tagName(accessor.componentType) ++ " is not supported."),
};
const start = view.byteOffset;
const length = component_length * accessor.count;
const end = start + length;
if (length != view.byteLength) return error.ParsingError;
return @as([]align(1) T, @ptrCast(buffer.bytes[start..end]));
}
};
const GltfBufferViewJson = struct {
buffer: u32,
byteLength: u32,
byteOffset: u32 = 0,
};
const GltfSamplerJson = struct {
magFilter: GltfFilterJson = .NEAREST,
minFilter: GltfFilterJson = .LINEAR,
wrapS: GltfWrapJson = .CLAMP_TO_EDGE,
wrapT: GltfWrapJson = .CLAMP_TO_EDGE,
const GltfFilterJson = enum(u32) {
NEAREST = 9728,
LINEAR = 9729,
NEAREST_MIPMAP_NEAREST = 9984,
LINEAR_MIPMAP_NEAREST = 9985,
NEAREST_MIPMAP_LINEAR = 9986,
LINEAR_MIPMAP_LINEAR = 9987,
};
const GltfWrapJson = enum(u32) {
CLAMP_TO_EDGE = 33071,
MIRRORED_REPEAT = 33648,
REPEAT = 10497,
};
};
const GltfBufferJson = struct {
// byteLength: u32,
uri: []u8,
};
};
pub fn load(path: []const u8, alloc: std.mem.Allocator) Assets.LoadError!@This() {
var json = Assets.load(.file, path);
defer Assets.free(json);
const parsed_gltf = try std.json.parseFromSlice(
GltfJson,
alloc,
(try json.getSync()).bytes,
.{ .ignore_unknown_fields = true },
);
defer parsed_gltf.deinit();
const gltf = &parsed_gltf.value;
if (gltf.scene >= gltf.scenes.len) return error.ParsingError;
const scene = &gltf.scenes[gltf.scene];
var buffers_init: u32 = 0;
const buffers = try alloc.alloc(Assets.File, gltf.buffers.len);
defer alloc.free(buffers);
defer for (buffers[0..buffers_init]) |buffer| {
Assets.free(buffer);
};
for (0.., buffers) |i, *buffer| {
const buffer_path = try std.fs.path.join(alloc, &.{ std.fs.path.dirname(path) orelse return error.ParsingError, gltf.buffers[i].uri });
defer alloc.free(buffer_path);
buffer.* = Assets.load(.file, buffer_path);
buffers_init += 1;
}
const nodes = try alloc.alloc(Node, scene.nodes.len);
errdefer alloc.free(nodes);
var meshes_init: u32 = 0;
const meshes = try alloc.alloc(Mesh, gltf.meshes.len);
errdefer alloc.free(nodes);
errdefer for (meshes[0..meshes_init]) |*mesh| {
for (mesh.primitives) |*primitive| {
sdl.ReleaseGPUBuffer(Graphics.device, primitive.vertex_buffer);
if (primitive.index_buffer) |buf| sdl.ReleaseGPUBuffer(Graphics.device, buf);
Assets.free(primitive.texture);
}
};
for (0.., nodes) |i, *node| {
const node_index = scene.nodes[i];
if (node_index >= gltf.nodes.len) return error.ParsingError;
const gltf_node = &gltf.nodes[node_index];
if (gltf_node.mesh >= gltf.meshes.len) return error.ParsingError;
node.mesh = gltf_node.mesh;
}
for (0.., meshes) |i, *mesh| {
const gltf_mesh = &gltf.meshes[i];
var primitivs_init: u32 = 0;
const primitives = try alloc.alloc(Primitive, gltf_mesh.primitives.len);
errdefer alloc.free(primitives);
errdefer for (primitives[0..primitivs_init]) |*primitive| {
sdl.ReleaseGPUBuffer(Graphics.device, primitive.vertex_buffer);
if (primitive.index_buffer) |buf| sdl.ReleaseGPUBuffer(Graphics.device, buf);
Assets.free(primitive.texture);
};
for (0.., primitives) |j, *primitive| {
const gltf_primitive = gltf_mesh.primitives[j];
if (gltf_primitive.attributes.POSITION >= gltf.accessors.len) return error.ParsingError;
if (gltf_primitive.attributes.TEXCOORD_0 >= gltf.accessors.len) return error.ParsingError;
if (gltf_primitive.material >= gltf.materials.len) return error.ParsingError;
const material = &gltf.materials[gltf_primitive.material];
const texture_index = material.pbrMetallicRoughness.baseColorTexture.index;
if (texture_index >= gltf.textures.len) return error.ParsingError;
const texture = &gltf.textures[texture_index];
if (texture.source >= gltf.images.len) return error.ParsingError;
const image = &gltf.images[texture.source];
const position = try gltf.accessors[gltf_primitive.attributes.POSITION].slice([3]f32, gltf.bufferViews, buffers) orelse return error.ParsingError;
const uv = try gltf.accessors[gltf_primitive.attributes.TEXCOORD_0].slice([2]f32, gltf.bufferViews, buffers) orelse return error.ParsingError;
const index = try gltf.accessors[gltf_primitive.indices].slice(u16, gltf.bufferViews, buffers) orelse return error.ParsingError;
primitive.vertex_buffer, primitive.index_buffer = try loadMesh(position, uv, index, alloc);
errdefer sdl.ReleaseGPUBuffer(Graphics.device, primitive.vertex_buffer);
errdefer sdl.ReleaseGPUBuffer(Graphics.device, primitive.index_buffer);
primitive.vertices = @intCast(position.len);
primitive.indices = @intCast(index.len);
const texture_path = try std.fs.path.join(alloc, &.{ std.fs.path.dirname(path) orelse return error.ParsingError, image.uri });
defer alloc.free(texture_path);
primitive.texture = Assets.load(.texture, texture_path);
primitivs_init += 1;
}
mesh.primitives = primitives;
meshes_init += 1;
}
return .{
.nodes = nodes,
.meshes = meshes,
};
}
pub fn unload(self: @This(), alloc: std.mem.Allocator) void {
for (self.meshes) |mesh| {
for (mesh.primitives) |*primitive| {
sdl.ReleaseGPUBuffer(Graphics.device, primitive.vertex_buffer);
sdl.ReleaseGPUBuffer(Graphics.device, primitive.index_buffer);
Assets.free(primitive.texture);
}
alloc.free(mesh.primitives);
}
alloc.free(self.meshes);
alloc.free(self.nodes);
}
pub fn loadMesh(position: []align(1) const [3]f32, uv: []align(1) const [2]f32, index: []align(1) const u16, alloc: std.mem.Allocator) !struct { *sdl.GPUBuffer, *sdl.GPUBuffer } {
if (position.len != uv.len) return error.ParsingError;
const vertices: u32 = @intCast(position.len);
const indices: u32 = @intCast(index.len);
const BYTES_PER_VERTEX = 20;
const BYTES_PER_INDEX = 2;
const vertex_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{
.size = vertices * BYTES_PER_VERTEX,
.usage = sdl.GPU_BUFFERUSAGE_VERTEX,
}) orelse return error.SdlError;
errdefer sdl.ReleaseGPUBuffer(Graphics.device, vertex_buffer);
const index_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{
.size = indices * 2,
.usage = sdl.GPU_BUFFERUSAGE_INDEX,
}) orelse return error.SdlError;
errdefer sdl.ReleaseGPUBuffer(Graphics.device, index_buffer);
const TRANSFER_CAPACITY = Graphics.TRANSFER_BUFFER_DEFAULT_CAPACITY;
const transfer_buffer = sdl.CreateGPUTransferBuffer(Graphics.device, &.{
.size = TRANSFER_CAPACITY,
.usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD,
}) orelse return error.SdlError;
defer sdl.ReleaseGPUTransferBuffer(Graphics.device, transfer_buffer);
const buffer = try alloc.alloc(u8, TRANSFER_CAPACITY);
defer alloc.free(buffer);
var vertices_uploaded: u32 = 0;
while (vertices_uploaded < vertices) {
const vertices_to_upload = @min(vertices - vertices_uploaded, TRANSFER_CAPACITY / BYTES_PER_VERTEX);
if (vertices_to_upload == 0) return error.FileTooBig;
for (0..vertices_to_upload) |i| {
const V = packed struct { x: f32, y: f32, z: f32, u: f32, v: f32 };
std.mem.copyForwards(
u8,
buffer[BYTES_PER_VERTEX * i ..],
&@as([BYTES_PER_VERTEX]u8, @bitCast(V{
.x = position[vertices_uploaded + i][0],
.y = position[vertices_uploaded + i][1],
.z = position[vertices_uploaded + i][2],
.u = uv[vertices_uploaded + i][0],
.v = uv[vertices_uploaded + i][1],
})),
);
}
const command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse return error.SdlError;
{
errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer);
const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return error.SdlError;
defer sdl.EndGPUCopyPass(copy_pass);
const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, transfer_buffer, false) orelse err.sdl());
@memcpy(map, buffer[0 .. vertices_to_upload * BYTES_PER_VERTEX]);
sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer);
sdl.UploadToGPUBuffer(copy_pass, &.{
.transfer_buffer = transfer_buffer,
}, &.{
.buffer = vertex_buffer,
.offset = vertices_uploaded * BYTES_PER_VERTEX,
.size = vertices_to_upload * BYTES_PER_VERTEX,
}, false);
}
vertices_uploaded += vertices_to_upload;
const fence = sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return error.SdlError;
defer sdl.ReleaseGPUFence(Graphics.device, fence);
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) return error.SdlError;
}
var indices_uploaded: u32 = 0;
while (indices_uploaded < indices) {
const indices_to_upload = @min(indices - indices_uploaded, TRANSFER_CAPACITY / BYTES_PER_INDEX);
if (indices_to_upload == 0) return error.FileTooBig;
for (0..indices_to_upload) |i| {
std.mem.copyForwards(
u8,
buffer[BYTES_PER_INDEX * i ..],
&@as([BYTES_PER_INDEX]u8, @bitCast(index[i])),
);
}
const command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse return error.SdlError;
{
errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer);
const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return error.SdlError;
defer sdl.EndGPUCopyPass(copy_pass);
const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, transfer_buffer, false) orelse err.sdl());
@memcpy(map, buffer[0 .. indices_to_upload * BYTES_PER_INDEX]);
sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer);
sdl.UploadToGPUBuffer(copy_pass, &.{
.transfer_buffer = transfer_buffer,
}, &.{
.buffer = index_buffer,
.offset = indices_uploaded * BYTES_PER_INDEX,
.size = indices_to_upload * BYTES_PER_INDEX,
}, false);
}
indices_uploaded += indices_to_upload;
const fence = sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return error.SdlError;
defer sdl.ReleaseGPUFence(Graphics.device, fence);
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) return error.SdlError;
}
return .{ vertex_buffer, index_buffer };
}

View File

@@ -1,7 +1,6 @@
const std = @import("std"); const std = @import("std");
const sdl = @import("sdl"); const sdl = @import("sdl");
const err = @import("../error.zig"); const err = @import("../error.zig");
const c = @import("../c.zig");
const Assets = @import("../assets.zig"); const Assets = @import("../assets.zig");
const Graphics = @import("../graphics.zig"); const Graphics = @import("../graphics.zig");
@@ -14,34 +13,30 @@ pub fn load(path: []const u8, alloc: std.mem.Allocator) Assets.LoadError!@This()
defer Assets.free(file); defer Assets.free(file);
const data = (file.getSync() catch return error.DependencyError).bytes; const data = (file.getSync() catch return error.DependencyError).bytes;
var width: u32 = undefined; const image: *sdl.Surface = @ptrCast(sdl.IMG_Load_IO(sdl.IOFromConstMem(data.ptr, data.len), true) orelse return error.ParsingError);
var height: u32 = undefined; defer sdl.DestroySurface(image);
var channels: u32 = undefined; const format = image.format;
const image = c.stbi_load_from_memory(
@ptrCast(data), const width: u32 = @intCast(image.w);
@intCast(data.len), const height: u32 = @intCast(image.h);
@ptrCast(&width), const channels: u32 = 4;
@ptrCast(&height),
@ptrCast(&channels), const image_slice = @as([*]u8, @ptrCast(image.pixels))[0..@intCast(width * height * channels)];
4,
);
if (image == null) return error.ParsingError;
defer c.stbi_image_free(image);
const image_slice = image[0..@intCast(width * height * channels)];
if (width > 8192 or height > 8192) return error.FileTooBig; if (width > 8192 or height > 8192) return error.FileTooBig;
const target_format = sdl.GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; const target_format = sdl.GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
const bytes_per_pixel = 4; const bytes_per_pixel = 4;
const mip_level = if(std.math.isPowerOfTwo(width) and width == height) @as(u32, Graphics.MIP_LEVEL) else @as(u32, 1); const mip_level = if (std.math.isPowerOfTwo(width) and width == height) @as(u32, Graphics.MIP_LEVEL) else @as(u32, 1);
const texture = Graphics.createTexture( const texture = sdl.CreateGPUTexture(Graphics.device, &.{
width, .width = width,
height, .height = height,
target_format, .layer_count_or_depth = 1,
sdl.GPU_TEXTUREUSAGE_SAMPLER | sdl.GPU_TEXTUREUSAGE_COLOR_TARGET, .format = target_format,
mip_level, .usage = sdl.GPU_TEXTUREUSAGE_SAMPLER | sdl.GPU_TEXTUREUSAGE_COLOR_TARGET,
); .num_levels = mip_level,
}) orelse err.sdl();
errdefer Graphics.freeTexture(texture); errdefer Graphics.freeTexture(texture);
const transfer_buffer_capacity = Graphics.TRANSFER_BUFFER_DEFAULT_CAPACITY; const transfer_buffer_capacity = Graphics.TRANSFER_BUFFER_DEFAULT_CAPACITY;
@@ -63,6 +58,43 @@ pub fn load(path: []const u8, alloc: std.mem.Allocator) Assets.LoadError!@This()
defer sdl.EndGPUCopyPass(copy_pass); defer sdl.EndGPUCopyPass(copy_pass);
const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, transfer_buffer, false) orelse err.sdl()); const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, transfer_buffer, false) orelse err.sdl());
var pixel: u32 = rows_uploaded * width * bytes_per_pixel;
var mapped: u32 = 0;
while (pixel < (rows_uploaded + rows_to_upload) * width * bytes_per_pixel) {
defer pixel += bytes_per_pixel;
defer mapped += bytes_per_pixel;
switch (format) {
// Convert to RGBA8888
sdl.PIXELFORMAT_ABGR8888 => {
map[mapped + 0] = image_slice[pixel + 3];
map[mapped + 1] = image_slice[pixel + 2];
map[mapped + 2] = image_slice[pixel + 1];
map[mapped + 3] = image_slice[pixel + 0];
},
sdl.PIXELFORMAT_ARGB8888 => {
map[mapped + 0] = image_slice[pixel + 1];
map[mapped + 1] = image_slice[pixel + 2];
map[mapped + 2] = image_slice[pixel + 3];
map[mapped + 3] = image_slice[pixel + 0];
},
sdl.PIXELFORMAT_RGBA8888 => {
map[mapped + 0] = image_slice[pixel + 0];
map[mapped + 1] = image_slice[pixel + 1];
map[mapped + 2] = image_slice[pixel + 2];
map[mapped + 3] = image_slice[pixel + 3];
},
sdl.PIXELFORMAT_BGRA8888 => {
map[mapped + 0] = image_slice[pixel + 2];
map[mapped + 1] = image_slice[pixel + 1];
map[mapped + 2] = image_slice[pixel + 0];
map[mapped + 3] = image_slice[pixel + 3];
},
else => {
sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer);
return error.UnsupportedAsset;
},
}
}
@memcpy(map, image_slice[(rows_uploaded * width * bytes_per_pixel)..((rows_uploaded + rows_to_upload) * width * bytes_per_pixel)]); @memcpy(map, image_slice[(rows_uploaded * width * bytes_per_pixel)..((rows_uploaded + rows_to_upload) * width * bytes_per_pixel)]);
sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer); sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer);

View File

@@ -1,8 +0,0 @@
pub usingnamespace @cImport({
@cDefine("STBI_NO_GIF", "1");
@cDefine("STBI_NO_JPEG", "1");
@cDefine("STBI_NO_HDR", "1");
@cDefine("STBI_NO_TGA", "1");
@cDefine("STB_IMAGE_IMPLEMENTATION", "1");
@cInclude("stb_image.h");
});

View File

@@ -14,7 +14,3 @@ const FileError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.Fil
pub fn file(err: FileError, path: []const u8) noreturn { pub fn file(err: FileError, path: []const u8) noreturn {
std.debug.panic("Error while reading \"{s}\": {any}", .{ path, err }); std.debug.panic("Error while reading \"{s}\": {any}", .{ path, err });
} }
pub fn stbi() noreturn {
std.debug.panic("STBI error!\n", .{});
}

View File

@@ -116,6 +116,18 @@ fn processEvents() void {
sdl.EVENT_MOUSE_WHEEL => { sdl.EVENT_MOUSE_WHEEL => {
Game.mouse.wheel += event.wheel.integer_y; Game.mouse.wheel += event.wheel.integer_y;
}, },
sdl.EVENT_WINDOW_RESIZED => {
if (event.window.data1 < 1 or event.window.data2 < 1) continue;
Graphics.window_width = @intCast(event.window.data1);
Graphics.window_height = @intCast(event.window.data2);
},
sdl.EVENT_WINDOW_PIXEL_SIZE_CHANGED => {
if (event.window.data1 < 1 or event.window.data2 < 1) continue;
Graphics.pixel_width = @intCast(event.window.data1);
Graphics.pixel_height = @intCast(event.window.data2);
},
else => {}, else => {},
} }
} }

View File

@@ -2,51 +2,89 @@ const std = @import("std");
const sdl = @import("sdl"); const sdl = @import("sdl");
const err = @import("error.zig"); const err = @import("error.zig");
const presets = @import("graphics/presets.zig"); const presets = @import("graphics/presets.zig");
const Game = @import("game.zig");
const Assets = @import("assets.zig"); const Assets = @import("assets.zig");
pub const Transform = @import("graphics/transform.zig"); pub const Transform = @import("graphics/transform.zig");
pub const Camera = @import("graphics/camera.zig"); pub const Camera = @import("graphics/camera.zig");
pub const Mesh = struct {
vertex_start: usize,
vertex_count: usize,
};
pub var window: *sdl.Window = undefined; pub var window: *sdl.Window = undefined;
pub var device: *sdl.GPUDevice = undefined; pub var device: *sdl.GPUDevice = undefined;
/// Only available while drawing /// Only available while drawing
var command_buffer: ?*sdl.GPUCommandBuffer = null; var command_buffer: ?*sdl.GPUCommandBuffer = null;
var render_pass: ?*sdl.GPURenderPass = null; var render_pass: ?*sdl.GPURenderPass = null;
var render_target: ?*sdl.GPUTexture = null; var render_target: ?*sdl.GPUTexture = null;
var batches: Batches = undefined;
var shader_vert: *sdl.GPUShader = undefined; var shader_vert: *sdl.GPUShader = undefined;
var shader_frag: *sdl.GPUShader = undefined; var shader_frag: *sdl.GPUShader = undefined;
var vertex_buffer: *sdl.GPUBuffer = undefined;
var vertex_buffer_capacity: usize = undefined;
var vertex_buffer_used: usize = undefined;
var transfer_buffer: *sdl.GPUTransferBuffer = undefined;
var transfer_buffer_capacity: usize = undefined;
var depth_texture: *sdl.GPUTexture = undefined; var depth_texture: *sdl.GPUTexture = undefined;
var fsaa_target: *sdl.GPUTexture = undefined; var antialias: Antialias = undefined;
var aa_target: *sdl.GPUTexture = undefined;
var pipeline: *sdl.GPUGraphicsPipeline = undefined; var pipeline: *sdl.GPUGraphicsPipeline = undefined;
pub var window_width: u32 = undefined; pub var window_width: u32 = undefined;
pub var window_height: u32 = undefined; pub var window_height: u32 = undefined;
var fsaa_scale: u32 = 4; pub var pixel_width: u32 = undefined;
var fsaa_level: u32 = 3; pub var pixel_height: u32 = undefined;
var render_width: u32 = undefined;
var render_height: u32 = undefined;
pub var camera: Camera = undefined; pub var camera: Camera = undefined;
const VERTEX_BUFFER_DEFAULT_CAPACITY = 1024;
const VERTEX_BUFFER_GROWTH_MULTIPLIER = 2;
const BYTES_PER_VERTEX = 5 * 4; const BYTES_PER_VERTEX = 5 * 4;
const DEPTH_FORMAT = sdl.GPU_TEXTUREFORMAT_D32_FLOAT; const DEPTH_FORMAT = sdl.GPU_TEXTUREFORMAT_D32_FLOAT;
pub const TRANSFER_BUFFER_DEFAULT_CAPACITY = 256 * 1024; pub const TRANSFER_BUFFER_DEFAULT_CAPACITY = 512 * 1024;
pub const MIP_LEVEL = 4; pub const MIP_LEVEL = 4;
const Antialias = union(enum) {
none,
msaa: enum(sdl.GPUSampleCount) {
@"2" = sdl.GPU_SAMPLECOUNT_2,
@"4" = sdl.GPU_SAMPLECOUNT_4,
@"8" = sdl.GPU_SAMPLECOUNT_8,
},
fsaa: enum {
@"2",
@"4",
@"8",
},
pub fn getMsaaSamples(self: @This()) sdl.GPUSampleCount {
if (self != .msaa) return sdl.GPU_SAMPLECOUNT_1;
return @intFromEnum(self.msaa);
}
pub fn getFsaaScale(self: @This()) u8 {
if (self != .fsaa) return 1;
return switch (self.fsaa) {
.@"2" => 2,
.@"4" => 4,
.@"8" => 8,
};
}
pub fn getFsaaLevel(self: @This()) u8 {
if (self != .fsaa) return 1;
return switch (self.fsaa) {
.@"2" => 2,
.@"4" => 3,
.@"8" => 4,
};
}
};
const Batch = struct {
object: *Assets.Object,
transform: Transform,
z: f32,
fn orderLessThan(ctx: void, lhs: Batch, rhs: Batch) bool {
_ = ctx;
return lhs.z > rhs.z;
}
};
const Batches = std.ArrayListUnmanaged(Batch);
const Graphics = @This(); const Graphics = @This();
pub fn create() void { pub fn create() void {
// Init // Init
@@ -59,8 +97,19 @@ pub fn create() void {
"", "",
1600, 1600,
900, 900,
sdl.WINDOW_VULKAN | sdl.WINDOW_RESIZABLE, sdl.WINDOW_VULKAN | sdl.WINDOW_RESIZABLE | sdl.WINDOW_HIGH_PIXEL_DENSITY,
) orelse err.sdl(); ) orelse err.sdl();
if (!sdl.GetWindowSizeInPixels(Graphics.window, @ptrCast(&Graphics.window_width), @ptrCast(&Graphics.window_height))) {
Graphics.window_width = 1600;
Graphics.window_height = 900;
}
const scale = sdl.GetWindowDisplayScale(Graphics.window);
Graphics.pixel_width = @intFromFloat(@round(scale * @as(f32, @floatFromInt(Graphics.window_width))));
Graphics.pixel_height = @intFromFloat(@round(scale * @as(f32, @floatFromInt(Graphics.window_height))));
Graphics.render_width = pixel_width;
Graphics.render_height = pixel_height;
Graphics.antialias = .none;
// Device // Device
Graphics.device = sdl.CreateGPUDevice( Graphics.device = sdl.CreateGPUDevice(
@@ -92,39 +141,27 @@ pub fn create() void {
}, },
); );
Graphics.vertex_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{
.usage = sdl.GPU_BUFFERUSAGE_VERTEX,
.size = VERTEX_BUFFER_DEFAULT_CAPACITY,
}) orelse err.sdl();
Graphics.vertex_buffer_capacity = VERTEX_BUFFER_DEFAULT_CAPACITY;
Graphics.vertex_buffer_used = 0;
Graphics.transfer_buffer = sdl.CreateGPUTransferBuffer(Graphics.device, &.{
.size = TRANSFER_BUFFER_DEFAULT_CAPACITY,
.usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD | sdl.GPU_TRANSFERBUFFERUSAGE_DOWNLOAD,
}) orelse err.sdl();
Graphics.transfer_buffer_capacity = TRANSFER_BUFFER_DEFAULT_CAPACITY;
const target_format = sdl.GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window); const target_format = sdl.GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
if (target_format == sdl.GPU_TEXTUREFORMAT_INVALID) err.sdl(); if (target_format == sdl.GPU_TEXTUREFORMAT_INVALID) err.sdl();
// TODO: Clean Graphics.depth_texture = sdl.CreateGPUTexture(device, &.{
if (!sdl.GetWindowSizeInPixels(Graphics.window, @ptrCast(&Graphics.window_width), @ptrCast(&Graphics.window_height))) err.sdl(); .width = Graphics.render_width * Graphics.antialias.getFsaaScale(),
.height = Graphics.render_height * Graphics.antialias.getFsaaScale(),
Graphics.depth_texture = createTexture( .layer_count_or_depth = 1,
@as(u32, @intCast(Graphics.window_width)) * Graphics.fsaa_scale, .format = DEPTH_FORMAT,
@as(u32, @intCast(Graphics.window_height)) * Graphics.fsaa_scale, .usage = sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
DEPTH_FORMAT, .num_levels = 1,
sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, .sample_count = Graphics.antialias.getMsaaSamples(),
1, }) orelse err.sdl();
); Graphics.aa_target = sdl.CreateGPUTexture(device, &.{
Graphics.fsaa_target = createTexture( .width = Graphics.render_width * Graphics.antialias.getFsaaScale(),
@as(u32, @intCast(Graphics.window_width)) * Graphics.fsaa_scale, .height = Graphics.render_height * Graphics.antialias.getFsaaScale(),
@as(u32, @intCast(Graphics.window_height)) * Graphics.fsaa_scale, .layer_count_or_depth = 1,
target_format, .format = target_format,
sdl.GPU_TEXTUREUSAGE_COLOR_TARGET | sdl.GPU_TEXTUREUSAGE_SAMPLER, .usage = if (Graphics.antialias == .fsaa) sdl.GPU_TEXTUREUSAGE_COLOR_TARGET | sdl.GPU_TEXTUREUSAGE_SAMPLER else sdl.GPU_TEXTUREUSAGE_COLOR_TARGET,
fsaa_level, .num_levels = Graphics.antialias.getFsaaLevel(),
); .sample_count = Graphics.antialias.getMsaaSamples(),
}) orelse err.sdl();
Graphics.pipeline = sdl.CreateGPUGraphicsPipeline(Graphics.device, &.{ Graphics.pipeline = sdl.CreateGPUGraphicsPipeline(Graphics.device, &.{
.vertex_shader = Graphics.shader_vert, .vertex_shader = Graphics.shader_vert,
@@ -152,6 +189,9 @@ pub fn create() void {
}, },
.num_vertex_attributes = 2, .num_vertex_attributes = 2,
}, },
.multisample_state = .{
.sample_count = Graphics.antialias.getMsaaSamples(),
},
.primitive_type = sdl.GPU_PRIMITIVETYPE_TRIANGLELIST, .primitive_type = sdl.GPU_PRIMITIVETYPE_TRIANGLELIST,
.rasterizer_state = presets.RASTERIZER_CULL, .rasterizer_state = presets.RASTERIZER_CULL,
.depth_stencil_state = presets.DEPTH_ENABLED, .depth_stencil_state = presets.DEPTH_ENABLED,
@@ -166,10 +206,11 @@ pub fn create() void {
}, },
}) orelse err.sdl(); }) orelse err.sdl();
Graphics.batches = Batches.empty;
Graphics.camera = Camera{ Graphics.camera = Camera{
.transform = .{}, .transform = .{},
.near = 1.0 / 16.0, .near = 1.0 / 16.0,
.far = 128.0,
.lens = 1.5, .lens = 1.5,
.aspect = 16.0 / 9.0, .aspect = 16.0 / 9.0,
.matrix = undefined, .matrix = undefined,
@@ -181,10 +222,8 @@ pub fn destroy() void {
sdl.DestroyWindow(Graphics.window); sdl.DestroyWindow(Graphics.window);
sdl.ReleaseGPUGraphicsPipeline(Graphics.device, Graphics.pipeline); sdl.ReleaseGPUGraphicsPipeline(Graphics.device, Graphics.pipeline);
sdl.ReleaseGPUTexture(Graphics.device, Graphics.fsaa_target); sdl.ReleaseGPUTexture(Graphics.device, Graphics.aa_target);
sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture); sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture);
sdl.ReleaseGPUBuffer(Graphics.device, Graphics.vertex_buffer);
sdl.ReleaseGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer);
sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_vert); sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_vert);
sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_frag); sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_frag);
@@ -194,98 +233,7 @@ pub fn destroy() void {
Graphics.command_buffer = null; Graphics.command_buffer = null;
} }
sdl.DestroyGPUDevice(Graphics.device); sdl.DestroyGPUDevice(Graphics.device);
} Graphics.batches.clearAndFree(Game.alloc);
pub fn loadMesh(mesh_bytes: []const u8) Mesh {
std.debug.assert(mesh_bytes.len < Graphics.transfer_buffer_capacity);
var size_mult: usize = 1;
while (Graphics.vertex_buffer_used + mesh_bytes.len > Graphics.vertex_buffer_capacity * size_mult) {
size_mult *= VERTEX_BUFFER_GROWTH_MULTIPLIER;
}
if (size_mult > 1) {
Graphics.growVertexBuffer(Graphics.vertex_buffer_capacity * size_mult);
}
const map = sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, true) orelse err.sdl();
@memcpy(@as([*]u8, @ptrCast(map)), mesh_bytes);
sdl.UnmapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer);
const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
const fence = blk: {
const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer) orelse err.sdl();
sdl.UploadToGPUBuffer(copy_pass, &.{
.transfer_buffer = Graphics.transfer_buffer,
.offset = 0,
}, &.{
.buffer = Graphics.vertex_buffer,
.offset = @intCast(Graphics.vertex_buffer_used),
.size = @intCast(mesh_bytes.len),
}, false);
sdl.EndGPUCopyPass(copy_pass);
break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(temp_command_buffer) orelse err.sdl();
};
defer sdl.ReleaseGPUFence(Graphics.device, fence);
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) err.sdl();
const vertex_start = Graphics.vertex_buffer_used;
Graphics.vertex_buffer_used += mesh_bytes.len;
return Mesh{
.vertex_start = vertex_start / BYTES_PER_VERTEX,
.vertex_count = mesh_bytes.len / BYTES_PER_VERTEX,
};
}
pub fn unloadMesh(mesh: Mesh) void {
// TODO: free some memory
_ = &mesh;
}
fn growVertexBuffer(new_size: usize) void {
const new_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{
.size = @intCast(new_size),
.usage = sdl.GPU_BUFFERUSAGE_VERTEX,
}) orelse err.sdl();
const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
const fence = blk: {
const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer);
var copied: usize = 0;
while (copied < Graphics.vertex_buffer_used) {
const to_transer = @min(Graphics.vertex_buffer_used - copied, Graphics.transfer_buffer_capacity);
sdl.DownloadFromGPUBuffer(copy_pass, &.{
.buffer = Graphics.vertex_buffer,
.offset = @intCast(copied),
.size = @intCast(to_transer),
}, &.{
.transfer_buffer = Graphics.transfer_buffer,
.offset = 0,
});
sdl.UploadToGPUBuffer(copy_pass, &.{
.transfer_buffer = Graphics.transfer_buffer,
.offset = 0,
}, &.{
.buffer = new_buffer,
.offset = @intCast(copied),
.size = @intCast(to_transer),
}, false);
copied += to_transer;
}
sdl.EndGPUCopyPass(copy_pass);
break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(temp_command_buffer) orelse err.sdl();
};
defer sdl.ReleaseGPUFence(Graphics.device, fence);
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) err.sdl();
sdl.ReleaseGPUBuffer(Graphics.device, Graphics.vertex_buffer);
Graphics.vertex_buffer = new_buffer;
Graphics.vertex_buffer_capacity = new_size;
} }
/// If window is minimized returns `false`, `render_pass` remains null /// If window is minimized returns `false`, `render_pass` remains null
@@ -299,22 +247,23 @@ pub fn beginDraw() bool {
// Window is probably hidden // Window is probably hidden
if (Graphics.render_target == null or width == 0 or height == 0) return false; if (Graphics.render_target == null or width == 0 or height == 0) return false;
if (width != Graphics.window_width or height != Graphics.window_height) { if (Graphics.render_width != Graphics.pixel_width or Graphics.render_height != Graphics.pixel_height) {
Graphics.resetTextures(width, height); Graphics.render_width = Graphics.pixel_width;
Graphics.camera.aspect = @as(f32, @floatFromInt(width)) / @as(f32, @floatFromInt(height)); Graphics.render_height = Graphics.pixel_height;
Graphics.window_width = width; Graphics.resetTextures(Graphics.render_width, Graphics.render_height);
Graphics.window_height = height; Graphics.camera.aspect = @as(f32, @floatFromInt(Graphics.render_width)) / @as(f32, @floatFromInt(Graphics.render_height));
} }
Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer.?, &.{ Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer.?, &.{
.clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 }, .clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
.cycle = false, .cycle = false,
.load_op = sdl.GPU_LOADOP_CLEAR, .load_op = sdl.GPU_LOADOP_DONT_CARE,
.store_op = sdl.GPU_STOREOP_STORE, .store_op = if (Graphics.antialias == .msaa) sdl.GPU_STOREOP_RESOLVE else sdl.GPU_STOREOP_STORE,
.mip_level = 0, .mip_level = 0,
.texture = Graphics.fsaa_target, .texture = if (Graphics.antialias == .fsaa or Graphics.antialias == .msaa) Graphics.aa_target else Graphics.render_target,
.resolve_texture = if (Graphics.antialias == .msaa) Graphics.render_target else null,
}, 1, &.{ }, 1, &.{
.clear_depth = 1.0, .clear_depth = 0.0,
.load_op = sdl.GPU_LOADOP_CLEAR, .load_op = sdl.GPU_LOADOP_CLEAR,
.store_op = sdl.GPU_STOREOP_DONT_CARE, .store_op = sdl.GPU_STOREOP_DONT_CARE,
.stencil_load_op = sdl.GPU_STOREOP_DONT_CARE, .stencil_load_op = sdl.GPU_STOREOP_DONT_CARE,
@@ -323,25 +272,52 @@ pub fn beginDraw() bool {
}) orelse err.sdl(); }) orelse err.sdl();
sdl.BindGPUGraphicsPipeline(Graphics.render_pass, Graphics.pipeline); sdl.BindGPUGraphicsPipeline(Graphics.render_pass, Graphics.pipeline);
sdl.BindGPUVertexBuffers(Graphics.render_pass, 0, &.{ .offset = 0, .buffer = Graphics.vertex_buffer }, 1);
Graphics.camera.computeMatrix(); Graphics.camera.computeMatrix();
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix, 16 * 4); sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix, 16 * 4);
return true; return true;
} }
pub fn clearDepth() void { fn finishPass() void {
std.sort.block(Batch, Graphics.batches.items, {}, Batch.orderLessThan);
for (Graphics.batches.items) |*batch| {
const asset_object = batch.object.get() orelse continue;
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 1, &batch.transform.matrix(), 16 * 4);
for (asset_object.nodes) |node| {
const mesh = &asset_object.meshes[node.mesh];
for (mesh.primitives) |*primitive| {
const asset_texture = primitive.texture.get() orelse continue;
sdl.BindGPUFragmentSamplers(Graphics.render_pass, 0, &sdl.GPUTextureSamplerBinding{
.texture = asset_texture.texture,
.sampler = asset_texture.sampler,
}, 1);
sdl.BindGPUVertexBuffers(Graphics.render_pass, 0, &.{ .offset = 0, .buffer = primitive.vertex_buffer }, 1);
sdl.BindGPUIndexBuffer(Graphics.render_pass, &.{ .buffer = primitive.index_buffer }, sdl.GPU_INDEXELEMENTSIZE_16BIT);
sdl.DrawGPUIndexedPrimitives(Graphics.render_pass, primitive.indices, 1, 0, 0, 0);
}
}
}
Graphics.batches.clearRetainingCapacity();
sdl.EndGPURenderPass(Graphics.render_pass.?); sdl.EndGPURenderPass(Graphics.render_pass.?);
}
pub fn clearDepth() void {
Graphics.finishPass();
Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer.?, &.{ Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer.?, &.{
.clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 }, .clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
.cycle = false, .cycle = false,
.load_op = sdl.GPU_LOADOP_LOAD, .load_op = sdl.GPU_LOADOP_LOAD,
.store_op = sdl.GPU_STOREOP_STORE, .store_op = if (Graphics.antialias == .msaa) sdl.GPU_STOREOP_RESOLVE else sdl.GPU_STOREOP_STORE,
.mip_level = 0, .mip_level = 0,
.texture = Graphics.fsaa_target, .texture = if (Graphics.antialias == .fsaa or Graphics.antialias == .msaa) Graphics.aa_target else Graphics.render_target,
.resolve_texture = if (Graphics.antialias == .msaa) Graphics.render_target else null,
}, 1, &.{ }, 1, &.{
.clear_depth = 1.0, .clear_depth = 0.0,
.load_op = sdl.GPU_LOADOP_CLEAR, .load_op = sdl.GPU_LOADOP_CLEAR,
.store_op = sdl.GPU_STOREOP_DONT_CARE, .store_op = sdl.GPU_STOREOP_DONT_CARE,
.stencil_load_op = sdl.GPU_STOREOP_DONT_CARE, .stencil_load_op = sdl.GPU_STOREOP_DONT_CARE,
@@ -350,40 +326,50 @@ pub fn clearDepth() void {
}) orelse err.sdl(); }) orelse err.sdl();
sdl.BindGPUGraphicsPipeline(Graphics.render_pass, Graphics.pipeline); sdl.BindGPUGraphicsPipeline(Graphics.render_pass, Graphics.pipeline);
sdl.BindGPUVertexBuffers(Graphics.render_pass, 0, &.{ .offset = 0, .buffer = Graphics.vertex_buffer }, 1);
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix, 16 * 4); sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix, 16 * 4);
} }
pub fn drawMesh(mesh: Mesh, texture: *Assets.Texture, transform: Transform) void { // `object`: pointer MUST be vaild until current render pass ends
pub fn drawObject(object: *Assets.Object, transform: Transform) void {
if (Graphics.render_pass == null) return; if (Graphics.render_pass == null) return;
const asset_texture = texture.get() orelse return;
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 1, &transform.matrix(), 16 * 4); @setFloatMode(.optimized);
sdl.BindGPUFragmentSamplers(Graphics.render_pass, 0, &sdl.GPUTextureSamplerBinding{ const z = Graphics.camera.matrix[8] * transform.position[0] +
.texture = asset_texture.texture, Graphics.camera.matrix[9] * transform.position[1] +
.sampler = asset_texture.sampler, Graphics.camera.matrix[10] * transform.position[2] +
}, 1); Graphics.camera.matrix[11];
sdl.DrawGPUPrimitives(Graphics.render_pass, @intCast(mesh.vertex_count), 1, @intCast(mesh.vertex_start), 0); var w = Graphics.camera.matrix[12] * transform.position[0] +
Graphics.camera.matrix[13] * transform.position[1] +
Graphics.camera.matrix[14] * transform.position[2] +
Graphics.camera.matrix[15];
if (w == 0) w = 1;
Graphics.batches.append(Game.alloc, .{
.object = object,
.transform = transform,
.z = z / w,
}) catch err.oom();
} }
pub fn endDraw() void { pub fn endDraw() void {
defer Graphics.command_buffer = null; defer Graphics.command_buffer = null;
defer Graphics.render_pass = null; defer Graphics.render_pass = null;
if (Graphics.render_pass) |pass| {
sdl.EndGPURenderPass(pass);
if (Graphics.fsaa_level > 1) sdl.GenerateMipmapsForGPUTexture(Graphics.command_buffer, Graphics.fsaa_target); Graphics.finishPass();
if (Graphics.antialias == .fsaa) {
sdl.GenerateMipmapsForGPUTexture(Graphics.command_buffer, Graphics.aa_target);
sdl.BlitGPUTexture(Graphics.command_buffer, &.{ sdl.BlitGPUTexture(Graphics.command_buffer, &.{
.source = .{ .source = .{
.texture = Graphics.fsaa_target, .texture = Graphics.aa_target,
.w = Graphics.window_width, .w = Graphics.render_width,
.h = Graphics.window_height, .h = Graphics.render_height,
.mip_level = fsaa_level - 1, .mip_level = Graphics.antialias.getFsaaLevel() - 1,
}, },
.destination = .{ .destination = .{
.texture = Graphics.render_target, .texture = Graphics.render_target,
.w = Graphics.window_width, .w = Graphics.render_width,
.h = Graphics.window_height, .h = Graphics.render_height,
}, },
.load_op = sdl.GPU_LOADOP_DONT_CARE, .load_op = sdl.GPU_LOADOP_DONT_CARE,
.filter = sdl.GPU_FILTER_NEAREST, .filter = sdl.GPU_FILTER_NEAREST,
@@ -396,7 +382,7 @@ fn loadShader(path: []const u8, info: sdl.GPUShaderCreateInfo) *sdl.GPUShader {
const file = std.fs.cwd().openFile(path, .{}) catch |e| err.file(e, path); const file = std.fs.cwd().openFile(path, .{}) catch |e| err.file(e, path);
defer file.close(); defer file.close();
const code = file.readToEndAllocOptions(std.heap.c_allocator, std.math.maxInt(usize), null, 1, 0) catch |e| err.file(e, path); const code = file.readToEndAllocOptions(std.heap.c_allocator, std.math.maxInt(usize), null, .@"1", 0) catch |e| err.file(e, path);
defer std.heap.c_allocator.free(code); defer std.heap.c_allocator.free(code);
var updated_info = info; var updated_info = info;
@@ -405,18 +391,6 @@ fn loadShader(path: []const u8, info: sdl.GPUShaderCreateInfo) *sdl.GPUShader {
return sdl.CreateGPUShader(device, &updated_info) orelse err.sdl(); return sdl.CreateGPUShader(device, &updated_info) orelse err.sdl();
} }
pub fn createTexture(width: u32, height: u32, format: c_uint, usage: c_uint, mip_level: u32) *sdl.GPUTexture {
return sdl.CreateGPUTexture(device, &.{
.format = format,
.layer_count_or_depth = 1,
.width = width,
.height = height,
.num_levels = mip_level,
.sample_count = sdl.GPU_SAMPLECOUNT_1,
.usage = usage,
}) orelse err.sdl();
}
pub fn freeTexture(texture: *sdl.GPUTexture) void { pub fn freeTexture(texture: *sdl.GPUTexture) void {
sdl.ReleaseGPUTexture(Graphics.device, texture); sdl.ReleaseGPUTexture(Graphics.device, texture);
} }
@@ -441,24 +415,32 @@ pub fn freeSampler(sampler: *sdl.GPUSampler) void {
fn resetTextures(width: u32, height: u32) void { fn resetTextures(width: u32, height: u32) void {
sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture); sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture);
Graphics.depth_texture = createTexture( Graphics.depth_texture = sdl.CreateGPUTexture(device, &.{
width * Graphics.fsaa_scale, .width = width * Graphics.antialias.getFsaaScale(),
height * Graphics.fsaa_scale, .height = height * Graphics.antialias.getFsaaScale(),
DEPTH_FORMAT, .layer_count_or_depth = 1,
sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, .format = DEPTH_FORMAT,
1, .usage = sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
); .num_levels = 1,
.sample_count = Graphics.antialias.getMsaaSamples(),
}) orelse err.sdl();
const target_format = sdl.GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window); const target_format = sdl.GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
sdl.ReleaseGPUTexture(Graphics.device, Graphics.fsaa_target); sdl.ReleaseGPUTexture(Graphics.device, Graphics.aa_target);
Graphics.fsaa_target = createTexture( Graphics.aa_target = sdl.CreateGPUTexture(device, &.{
width * Graphics.fsaa_scale, .width = width * Graphics.antialias.getFsaaScale(),
height * Graphics.fsaa_scale, .height = height * Graphics.antialias.getFsaaScale(),
target_format, .layer_count_or_depth = 1,
sdl.GPU_TEXTUREUSAGE_COLOR_TARGET | sdl.GPU_TEXTUREUSAGE_SAMPLER, .format = target_format,
fsaa_level, .usage = switch (Graphics.antialias) {
); .none => sdl.GPU_TEXTUREUSAGE_COLOR_TARGET,
.fsaa => sdl.GPU_TEXTUREUSAGE_COLOR_TARGET | sdl.GPU_TEXTUREUSAGE_SAMPLER,
.msaa => sdl.GPU_TEXTUREUSAGE_COLOR_TARGET,
},
.num_levels = Graphics.antialias.getFsaaLevel(),
.sample_count = Graphics.antialias.getMsaaSamples(),
}) orelse err.sdl();
} }
pub fn windowId() sdl.WindowID { pub fn windowId() sdl.WindowID {

View File

@@ -8,7 +8,6 @@ transform: Transform,
/// tangent of the half of the view angle (90 degress = 1 "lens") /// tangent of the half of the view angle (90 degress = 1 "lens")
lens: f32, lens: f32,
near: f32, near: f32,
far: f32,
/// width = height * aspect /// width = height * aspect
aspect: f32, aspect: f32,
@@ -19,14 +18,11 @@ pub fn computeMatrix(camera: *Camera) void {
const xx = 1.0 / (camera.lens * camera.aspect); const xx = 1.0 / (camera.lens * camera.aspect);
const yy = 1.0 / camera.lens; const yy = 1.0 / camera.lens;
const fnmod = 1.0 / (camera.far - camera.near);
const zz = camera.far * fnmod;
const wz = -camera.near * camera.far * fnmod;
const projection = @Vector(16, f32){ const projection = @Vector(16, f32){
xx, 0, 0, 0, xx, 0, 0, 0,
0, yy, 0, 0, 0, yy, 0, 0,
0, 0, -zz, wz, 0, 0, 0, camera.near,
0, 0, -1, 0, 0, 0, -1, 0,
}; };
camera.matrix = Transform.multiplyMatrix(projection, camera.transform.inverseMatrix()); camera.matrix = Transform.multiplyMatrix(projection, camera.transform.inverseMatrix());
} }
@@ -61,9 +57,9 @@ pub fn mouse_in_quad(camera: Camera, mouse: @Vector(2, f32), quad_transform: Tra
const hh = height * 0.5; const hh = height * 0.5;
const pi: [4]@Vector(2, f32) = .{ const pi: [4]@Vector(2, f32) = .{
.{ -hw, -hh }, .{ -hw, -hh },
.{ -hw, hh }, .{ -hw, hh },
.{ hw, hh }, .{ hw, hh },
.{ hw, -hh }, .{ hw, -hh },
}; };
var po: [4]@Vector(2, f32) = undefined; var po: [4]@Vector(2, f32) = undefined;
for (0..4) |i| { for (0..4) |i| {

View File

@@ -12,7 +12,7 @@ pub const BLEND_NORMAL = sdl.GPUColorTargetBlendState{
}; };
pub const DEPTH_ENABLED = sdl.GPUDepthStencilState{ pub const DEPTH_ENABLED = sdl.GPUDepthStencilState{
.compare_op = sdl.GPU_COMPAREOP_LESS, .compare_op = sdl.GPU_COMPAREOP_GREATER,
.enable_depth_test = true, .enable_depth_test = true,
.enable_depth_write = true, .enable_depth_write = true,
}; };

View File

@@ -12,13 +12,9 @@ const Order = i32;
pub var object_map: std.AutoHashMapUnmanaged(Id, usize) = .{}; pub var object_map: std.AutoHashMapUnmanaged(Id, usize) = .{};
pub var objects: std.ArrayListUnmanaged(Object) = .{}; pub var objects: std.ArrayListUnmanaged(Object) = .{};
pub var hand_mesh: Graphics.Mesh = undefined; pub var hand: Assets.Object = undefined;
pub var cube_mesh: Graphics.Mesh = undefined; pub var table: Assets.Object = undefined;
pub var table_mesh: Graphics.Mesh = undefined; pub var cubemap: Assets.Object = undefined;
pub var cubemap_mesh: Graphics.Mesh = undefined;
pub var table_texture: Assets.Texture = undefined;
pub var hand_texture: Assets.Texture = undefined;
pub var cubemap_texture: Assets.Texture = undefined;
pub var camera_position: @Vector(2, f32) = @splat(0); pub var camera_position: @Vector(2, f32) = @splat(0);
pub var hand_transform: Graphics.Transform = .{}; pub var hand_transform: Graphics.Transform = .{};
@@ -46,8 +42,7 @@ const Object = struct {
target_transform: Graphics.Transform = .{}, target_transform: Graphics.Transform = .{},
width: f32, width: f32,
height: f32, height: f32,
mesh: Graphics.Mesh, object: Assets.Object,
texture: Assets.Texture,
order: Order, order: Order,
id: Id, id: Id,
index: u32, index: u32,
@@ -99,15 +94,7 @@ pub fn initDebug() void {
.type = .card, .type = .card,
.width = 0.5, .width = 0.5,
.height = 0.5, .height = 0.5,
.mesh = Graphics.loadMesh(@ptrCast(&Graphics.generatePlane( .object = Assets.load(.gltf, "data/yakuza/card.gltf"),
@as(f32, @floatFromInt(9 + i / 10)) / 16.0,
@as(f32, @floatFromInt(0 + i % 10)) / 16.0,
@as(f32, @floatFromInt(10 + i / 10)) / 16.0,
@as(f32, @floatFromInt(1 + i % 10)) / 16.0,
0.5,
0.5,
))),
.texture = Assets.load(.texture, "data/yakuza.png"),
.order = @intCast(i), .order = @intCast(i),
.id = @intCast(i), .id = @intCast(i),
.index = @intCast(i), .index = @intCast(i),
@@ -120,15 +107,7 @@ pub fn initDebug() void {
.type = .deck, .type = .deck,
.width = 1, .width = 1,
.height = 1, .height = 1,
.mesh = Graphics.loadMesh(@ptrCast(&Graphics.generatePlane( .object = Assets.load(.gltf, "data/yakuza/pad.gltf"),
0.0 / 8.0,
4.0 / 8.0,
1.0 / 8.0,
5.0 / 8.0,
1,
1,
))),
.texture = Assets.load(.texture, "data/yakuza.png"),
.order = 70, .order = 70,
.id = 70, .id = 70,
.index = 70, .index = 70,
@@ -139,27 +118,16 @@ pub fn initDebug() void {
.type = .deck, .type = .deck,
.width = 1, .width = 1,
.height = 1, .height = 1,
.mesh = Graphics.loadMesh(@ptrCast(&Graphics.generatePlane( .object = Assets.load(.gltf, "data/yakuza/pad.gltf"),
0.0 / 8.0,
4.0 / 8.0,
1.0 / 8.0,
5.0 / 8.0,
1,
1,
))),
.texture = Assets.load(.texture, "data/yakuza.png"),
.order = 71, .order = 71,
.id = 71, .id = 71,
.index = 71, .index = 71,
}) catch err.oom(); }) catch err.oom();
World.object_map.put(Game.alloc, 71, 71) catch err.oom(); World.object_map.put(Game.alloc, 71, 71) catch err.oom();
World.table_mesh = Graphics.loadMesh(@ptrCast(&Graphics.generatePlane(0, 0, 0.5, 0.5, 8, 8))); World.hand = Assets.load(.gltf, "data/hand.gltf");
World.hand_mesh = Graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA)); World.table = Assets.load(.gltf, "data/yakuza/table.gltf");
World.cubemap_mesh = Graphics.loadMesh(@ptrCast(&CUBEMAP_MESH_DATA)); World.cubemap = Assets.load(.gltf, "data/cubemap.gltf");
World.table_texture = Assets.load(.texture, "data/yakuza.png");
World.hand_texture = Assets.load(.texture, "data/hand.png");
World.cubemap_texture = Assets.load(.texture, "data/cubemap.png");
World.camera_position = @splat(0); World.camera_position = @splat(0);
World.hand_transform = .{}; World.hand_transform = .{};
@@ -177,15 +145,11 @@ pub fn initDebug() void {
} }
pub fn deinit() void { pub fn deinit() void {
Graphics.unloadMesh(World.table_mesh); Assets.free(World.hand);
Graphics.unloadMesh(World.hand_mesh); Assets.free(World.table);
Graphics.unloadMesh(World.cubemap_mesh); Assets.free(World.cubemap);
Assets.free(World.table_texture);
Assets.free(World.hand_texture);
Assets.free(World.cubemap_texture);
for (World.objects.items) |*object| { for (World.objects.items) |*object| {
Assets.free(object.texture); Assets.free(object.object);
Graphics.unloadMesh(object.mesh);
} }
World.objects.clearAndFree(Game.alloc); World.objects.clearAndFree(Game.alloc);
World.object_map.clearAndFree(Game.alloc); World.object_map.clearAndFree(Game.alloc);
@@ -424,8 +388,9 @@ pub fn updateObject(object: *Object, delta: f32) void {
.dock => { .dock => {
var topleft_x = -World.dock_last_width * 0.5 * DOCK_TILT_COS + World.dock_spacing * (@as(f32, @floatFromInt(object.parent_index)) - @as(f32, @floatFromInt(World.dock_objects - 1)) * 0.5); var topleft_x = -World.dock_last_width * 0.5 * DOCK_TILT_COS + World.dock_spacing * (@as(f32, @floatFromInt(object.parent_index)) - @as(f32, @floatFromInt(World.dock_objects - 1)) * 0.5);
const total_w = @as(f32, @floatFromInt(World.dock_objects - 1)) * World.dock_spacing + World.dock_last_width * DOCK_TILT_COS; const total_w = @as(f32, @floatFromInt(World.dock_objects - 1)) * World.dock_spacing + World.dock_last_width * DOCK_TILT_COS;
const mouse_x = if (World.dock_focused) Game.mouse.x_norm else 0.5;
if (total_w > Graphics.camera.aspect * 2) { if (total_w > Graphics.camera.aspect * 2) {
topleft_x += math.lerp(0, Graphics.camera.aspect - total_w * 0.5, Game.mouse.x_norm); topleft_x += math.lerp(0, Graphics.camera.aspect - total_w * 0.5, mouse_x);
} }
const hit = World.hover == object.id; const hit = World.hover == object.id;
const topleft_y = if (World.dock_focused) if (hit) @as(f32, 0.5) else @as(f32, 0.3) else @as(f32, 0.2); const topleft_y = if (World.dock_focused) if (hit) @as(f32, 0.5) else @as(f32, 0.3) else @as(f32, 0.2);
@@ -465,12 +430,12 @@ pub fn updateObject(object: *Object, delta: f32) void {
} }
pub fn draw() void { pub fn draw() void {
Graphics.drawMesh(World.table_mesh, &World.table_texture, .{}); Graphics.drawObject(&World.table, .{});
for (World.objects.items) |*object| { for (World.objects.items) |*object| {
sw: switch (object.parent) { sw: switch (object.parent) {
.none, .hand => { .none, .hand => {
Graphics.drawMesh(object.mesh, &object.texture, object.drawingTransform()); Graphics.drawObject(&object.object, object.drawingTransform());
}, },
.dock => {}, .dock => {},
.deck => |id| { .deck => |id| {
@@ -482,9 +447,8 @@ pub fn draw() void {
} }
} }
Graphics.drawMesh( Graphics.drawObject(
World.hand_mesh, &World.hand,
&World.hand_texture,
Graphics.Transform.combineTransforms( Graphics.Transform.combineTransforms(
.{ .{
.position = .{ World.hand_scale * 0.5, -World.hand_scale * 0.5, 0 }, .position = .{ World.hand_scale * 0.5, -World.hand_scale * 0.5, 0 },
@@ -494,14 +458,14 @@ pub fn draw() void {
), ),
); );
Graphics.drawMesh(World.cubemap_mesh, &World.cubemap_texture, .{ Graphics.drawObject(&World.cubemap, .{
.scale = Graphics.camera.far, .scale = 1e20,
.position = Graphics.camera.transform.position, .position = Graphics.camera.transform.position,
}); });
Graphics.clearDepth(); Graphics.clearDepth();
for (World.objects.items) |*object| { for (World.objects.items) |*object| {
if (object.parent == .dock) if (object.parent == .dock)
Graphics.drawMesh(object.mesh, &object.texture, object.drawingTransform()); Graphics.drawObject(&object.object, object.drawingTransform());
} }
} }
@@ -580,65 +544,3 @@ fn objectOrderLessThan(ctx: void, lhs: Object, rhs: Object) bool {
_ = ctx; _ = ctx;
return lhs.order < rhs.order; return lhs.order < rhs.order;
} }
const T1 = 1.0 / 3.0;
const T2 = 2.0 / 3.0;
const CUBEMAP_MESH_DATA = [_]f32{
-0.5, 0.5, -0.5, T2, 0,
-0.5, -0.5, -0.5, T2, 0.5,
0.5, 0.5, -0.5, 1, 0,
0.5, -0.5, -0.5, 1, 0.5,
0.5, 0.5, -0.5, 1, 0,
-0.5, -0.5, -0.5, T2, 0.5,
0.5, 0.5, -0.5, 0, 1,
0.5, -0.5, -0.5, T1, 1,
0.5, 0.5, 0.5, 0, 0.5,
0.5, -0.5, 0.5, T1, 0.5,
0.5, 0.5, 0.5, 0, 0.5,
0.5, -0.5, -0.5, T1, 1,
0.5, 0.5, 0.5, 1.0, 0.0,
0.5, -0.5, 0.5, 1.0, 1.0,
-0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 1.0,
-0.5, 0.5, 0.5, 0.0, 0.0,
0.5, -0.5, 0.5, 1.0, 1.0,
-0.5, 0.5, 0.5, T1, 0,
-0.5, -0.5, 0.5, 0, 0,
-0.5, 0.5, -0.5, T1, 0.5,
-0.5, -0.5, -0.5, 0, 0.5,
-0.5, 0.5, -0.5, T1, 0.5,
-0.5, -0.5, 0.5, 0, 0,
-0.5, 0.5, 0.5, T1, 0.5,
-0.5, 0.5, -0.5, T1, 1,
0.5, 0.5, 0.5, T2, 0.5,
0.5, 0.5, -0.5, T2, 1,
0.5, 0.5, 0.5, T2, 0.5,
-0.5, 0.5, -0.5, T1, 1,
-0.5, -0.5, -0.5, T2, 0.5,
-0.5, -0.5, 0.5, T2, 0,
0.5, -0.5, -0.5, T1, 0.5,
0.5, -0.5, 0.5, T1, 0,
0.5, -0.5, -0.5, T1, 0.5,
-0.5, -0.5, 0.5, T2, 0,
};
const PLANE_MESH_DATA = [_]f32{
-0.5, -0.5, 0, 0.0, 1.0,
0.5, 0.5, 0, 1.0, 0.0,
-0.5, 0.5, 0, 0.0, 0.0,
0.5, 0.5, 0, 1.0, 0.0,
-0.5, -0.5, 0, 0.0, 1.0,
0.5, -0.5, 0, 1.0, 1.0,
};
const PLANE_MESH_DATA_HALF = [_]f32{
-0.25, -0.25, 0, 0.0, 1.0,
0.25, 0.25, 0, 1.0, 0.0,
-0.25, 0.25, 0, 0.0, 0.0,
0.25, 0.25, 0, 1.0, 0.0,
-0.25, -0.25, 0, 0.0, 1.0,
0.25, -0.25, 0, 1.0, 1.0,
};

View File

@@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const sdl = @cImport({ const sdl = @cImport({
@cInclude("SDL3/SDL.h"); @cInclude("SDL3/SDL.h");
@cInclude("SDL3_image/SDL_image.h");
}); });
const PREFIX = "SDL_"; const PREFIX = "SDL_";
@@ -23,12 +24,8 @@ pub fn main() !void {
try std.fs.cwd().createFile(output, .{}); try std.fs.cwd().createFile(output, .{});
defer file.close(); defer file.close();
const writer = file.writer(); var writer_buffer: [4096]u8 = undefined;
var writer = file.writer(&writer_buffer);
const stdout = std.io.getStdOut();
defer stdout.close();
const out = stdout.writer();
var renamed_count: usize = 0; var renamed_count: usize = 0;
for (@typeInfo(sdl).@"struct".decls) |decl| { for (@typeInfo(sdl).@"struct".decls) |decl| {
@@ -36,7 +33,7 @@ pub fn main() !void {
const new_name: []const u8 = decl.name[PREFIX.len..]; const new_name: []const u8 = decl.name[PREFIX.len..];
try writer.print( try writer.interface.print(
\\#define {1s} {0s} \\#define {1s} {0s}
\\ \\
, .{ decl.name, new_name }); , .{ decl.name, new_name });
@@ -45,5 +42,4 @@ pub fn main() !void {
if (renamed_count == 0) { if (renamed_count == 0) {
@panic("No SDL definitions renamed"); @panic("No SDL definitions renamed");
} }
try out.print("[SDL Translator] {} SDL definitions renamed\n", .{renamed_count});
} }