diff --git a/.gitignore b/.gitignore index 5858b8d..47e7af4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .zig-cache/ zig-pkg/ +zig-out/ diff --git a/README.md b/README.md index 23587e6..7ca8da6 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ src/rhi/vulkan/resource.zig | `makeX(...)` | Creates an RHI resource and returns its handle | | `deleteX(handle)` | Releases the resource and removes it from the resource pool | | `releaseX(handle)` | Releases the resource without removing it from the resource pool | +| `getXInfo(handle)` | Returns backend-independent information about the resource | | `getRawVkX(handle)` | Returns the backend-specific raw Vulkan GPU handle | ## Example diff --git a/build.zig b/build.zig index d35820f..26443bb 100644 --- a/build.zig +++ b/build.zig @@ -58,6 +58,14 @@ pub fn build(b: *std.Build) !void { mod.addImport("stb_image", stb_image.createModule()); mod.addCSourceFile(.{ .file = b.path("src/vendor/stb_image/stb_image.c") }); + const fast_obj = b.addTranslateC(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("./src/vendor/fast_obj/fast_obj.h"), + }); + mod.addImport("fast_obj", fast_obj.createModule()); + mod.addCSourceFiles(.{ .files = &.{"src/vendor/fast_obj/fast_obj.c"} }); + // // EXAMPLES // @@ -67,6 +75,7 @@ pub fn build(b: *std.Build) !void { src: []const u8, }{ .{ .name = "simple", .src = "examples/simple/simple.zig" }, + .{ .name = "3D", .src = "examples/simple_3D/simple_3D.zig" }, }) |excfg| { const ex_name = excfg.name; const ex_src = excfg.src; @@ -110,13 +119,19 @@ pub fn build(b: *std.Build) !void { example.root_module.addImport("sdl", sdl.createModule()); example.root_module.linkSystemLibrary("SDL3", .{}); + const example_math = b.dependency("NYXMath", .{ + .target = target, + .optimize = optimize, + }); + example.root_module.addImport("math", example_math.module("NYXMath")); + const example_run = b.addRunArtifact(example); example_run_step.dependOn(&example_run.step); const example_build_step = b.addInstallArtifact(example, .{}); example_step.dependOn(&example_build_step.step); - const check_step = b.step("check", "Check if compiles"); - check_step.dependOn(&example.step); + // const check_step = b.step("check", "Check if compiles"); + // check_step.dependOn(&example.step); } } diff --git a/compile.sh b/compile.sh index 6fa4af0..57e48ee 100755 --- a/compile.sh +++ b/compile.sh @@ -1,3 +1,7 @@ clear + glslc examples/assets/shaders/simple.vert -o examples/assets/shaders/compiled/simple.vert.spv glslc examples/assets/shaders/simple.frag -o examples/assets/shaders/compiled/simple.frag.spv + +glslc examples/assets/shaders/3D.vert -o examples/assets/shaders/compiled/3D.vert.spv +glslc examples/assets/shaders/3D.frag -o examples/assets/shaders/compiled/3D.frag.spv diff --git a/examples/assets/shaders/3D.frag b/examples/assets/shaders/3D.frag new file mode 100644 index 0000000..a2dd610 --- /dev/null +++ b/examples/assets/shaders/3D.frag @@ -0,0 +1,26 @@ +#version 450 + +// +// IO +// + +layout(location = 0) in vec2 in_UV; + +layout(location = 0) out vec4 out_color; + +// +// UNIFORMS +// + +// layout(binding = 0) uniform UnlitMaterial { +// vec4 albedo; +// } material; + +layout(binding = 2) uniform sampler2D albedo_texture; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +void main() { + // out_color = vec4(1.0); + out_color = texture(albedo_texture, in_UV); +} diff --git a/examples/assets/shaders/3D.vert b/examples/assets/shaders/3D.vert new file mode 100644 index 0000000..8fb49fe --- /dev/null +++ b/examples/assets/shaders/3D.vert @@ -0,0 +1,45 @@ +#version 450 + +// +// IN +// + +layout(location = 0) in vec3 in_position; +layout(location = 1) in vec3 in_normal; +layout(location = 2) in vec2 in_UV; + +// +// OUT +// + +layout(location = 0) out vec2 out_UV; +layout(location = 1) out vec3 out_normal; +layout(location = 2) out vec3 out_world_position; + +// +// UNIFORMS +// + +layout(set = 0, binding = 0) uniform Camera { + mat4 projection; + mat4 view; +} camera; + +layout(set = 0, binding = 1) uniform Model { + mat4 model; +} object; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +void main() { + // --- GET WORLD POSITION --- + vec4 world_position = object.model * vec4(in_position, 1.0); + + // --- SHOW POSITION AT --- + gl_Position = camera.projection * camera.view * world_position; + + // --- SET --- + out_UV = in_UV; + out_normal = mat3(transpose(inverse(object.model))) * in_normal; + out_world_position = world_position.xyz; +} diff --git a/examples/assets/shaders/compiled/3D.frag.spv b/examples/assets/shaders/compiled/3D.frag.spv new file mode 100644 index 0000000..6f9262d Binary files /dev/null and b/examples/assets/shaders/compiled/3D.frag.spv differ diff --git a/examples/assets/shaders/compiled/3D.vert.spv b/examples/assets/shaders/compiled/3D.vert.spv new file mode 100644 index 0000000..13b49d9 Binary files /dev/null and b/examples/assets/shaders/compiled/3D.vert.spv differ diff --git a/examples/simple/simple.zig b/examples/simple/simple.zig index 598c69c..4969da6 100644 --- a/examples/simple/simple.zig +++ b/examples/simple/simple.zig @@ -77,15 +77,17 @@ pub fn main(init: std.process.Init) !void { }, init.gpa, init.io); defer gfx.deinit(); + errdefer gfx.deinit(); + // --- # --- - const adapter = try gfx.makeAdapter(.{}); + const adapter = try gfx.makeAdapter(.{ .gpu_type = .any }); const surface = try gfx.makeSurface(); const device = try gfx.makeDevice(adapter, surface); const swapchain = try gfx.makeSwapchain(adapter, device, surface); // --- TEXTURES --- const texture = try gfx.makeTexture(.{ - .image = .{ .path = "examples/assets/textures/texture.jpg" }, + .image = .{ .path = "examples/assets/textures/bully.jpg" }, .sampler = .{ .min_filter = .linear, .address_u = .repeat }, }, adapter, device); @@ -107,10 +109,12 @@ pub fn main(init: std.process.Init) !void { .vertex_binding = .{ .stride = @sizeOf(Vertex) }, .uniforms = &.{ - .{ .name = "material", .offset = 0, .size = @sizeOf(UnlitMaterial) }, + .{ .name = "material", .size = @sizeOf(UnlitMaterial) }, }, - .textures = &.{texture}, + .textures = &.{ + texture, + }, .blend_mode = .alpha, diff --git a/examples/simple_3D/simple_3D.zig b/examples/simple_3D/simple_3D.zig new file mode 100644 index 0000000..ab80bf8 --- /dev/null +++ b/examples/simple_3D/simple_3D.zig @@ -0,0 +1,253 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const std = @import("std"); +const sdl = @import("sdl"); +const math = @import("math"); +const Gfx = @import("gfx"); + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +const UnlitMaterial = struct { + albedo: [4]f32 = .{ 1, 1, 1, 1 }, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +const Vertex = struct { + pos: [3]f32, + normal: [3]f32, + uv: [2]f32, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +const vertices = [_]Vertex{ + .{ .pos = .{ -0.5, -0.5, 0.5 }, .normal = .{ 0, 0, 1 }, .uv = .{ 0, 0 } }, + .{ .pos = .{ 0.5, -0.5, 0.5 }, .normal = .{ 0, 0, 1 }, .uv = .{ 1, 0 } }, + .{ .pos = .{ 0.5, 0.5, 0.5 }, .normal = .{ 0, 0, 1 }, .uv = .{ 1, 1 } }, + .{ .pos = .{ -0.5, 0.5, 0.5 }, .normal = .{ 0, 0, 1 }, .uv = .{ 0, 1 } }, + + .{ .pos = .{ 0.5, -0.5, -0.5 }, .normal = .{ 0, 0, -1 }, .uv = .{ 0, 0 } }, + .{ .pos = .{ -0.5, -0.5, -0.5 }, .normal = .{ 0, 0, -1 }, .uv = .{ 1, 0 } }, + .{ .pos = .{ -0.5, 0.5, -0.5 }, .normal = .{ 0, 0, -1 }, .uv = .{ 1, 1 } }, + .{ .pos = .{ 0.5, 0.5, -0.5 }, .normal = .{ 0, 0, -1 }, .uv = .{ 0, 1 } }, + + .{ .pos = .{ -0.5, -0.5, -0.5 }, .normal = .{ -1, 0, 0 }, .uv = .{ 0, 0 } }, + .{ .pos = .{ -0.5, -0.5, 0.5 }, .normal = .{ -1, 0, 0 }, .uv = .{ 1, 0 } }, + .{ .pos = .{ -0.5, 0.5, 0.5 }, .normal = .{ -1, 0, 0 }, .uv = .{ 1, 1 } }, + .{ .pos = .{ -0.5, 0.5, -0.5 }, .normal = .{ -1, 0, 0 }, .uv = .{ 0, 1 } }, + + .{ .pos = .{ 0.5, -0.5, 0.5 }, .normal = .{ 1, 0, 0 }, .uv = .{ 0, 0 } }, + .{ .pos = .{ 0.5, -0.5, -0.5 }, .normal = .{ 1, 0, 0 }, .uv = .{ 1, 0 } }, + .{ .pos = .{ 0.5, 0.5, -0.5 }, .normal = .{ 1, 0, 0 }, .uv = .{ 1, 1 } }, + .{ .pos = .{ 0.5, 0.5, 0.5 }, .normal = .{ 1, 0, 0 }, .uv = .{ 0, 1 } }, + + .{ .pos = .{ -0.5, 0.5, 0.5 }, .normal = .{ 0, 1, 0 }, .uv = .{ 0, 0 } }, + .{ .pos = .{ 0.5, 0.5, 0.5 }, .normal = .{ 0, 1, 0 }, .uv = .{ 1, 0 } }, + .{ .pos = .{ 0.5, 0.5, -0.5 }, .normal = .{ 0, 1, 0 }, .uv = .{ 1, 1 } }, + .{ .pos = .{ -0.5, 0.5, -0.5 }, .normal = .{ 0, 1, 0 }, .uv = .{ 0, 1 } }, + + .{ .pos = .{ -0.5, -0.5, -0.5 }, .normal = .{ 0, -1, 0 }, .uv = .{ 0, 0 } }, + .{ .pos = .{ 0.5, -0.5, -0.5 }, .normal = .{ 0, -1, 0 }, .uv = .{ 1, 0 } }, + .{ .pos = .{ 0.5, -0.5, 0.5 }, .normal = .{ 0, -1, 0 }, .uv = .{ 1, 1 } }, + .{ .pos = .{ -0.5, -0.5, 0.5 }, .normal = .{ 0, -1, 0 }, .uv = .{ 0, 1 } }, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +const indices = [_]u32{ + 0, 1, 2, + 0, 2, 3, + + 4, 5, 6, + 4, 6, 7, + + 8, 9, 10, + 8, 10, 11, + + 12, 13, 14, + 12, 14, 15, + + 16, 17, 18, + 16, 18, 19, + + 20, 21, 22, + 20, 22, 23, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +const Camera = struct { + projection: math.Mat4x4, + view: math.Mat4x4, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +const Model = struct { + matrix: math.Mat4x4, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn main(init: std.process.Init) !void { + + // + // DATA + // + + var material: UnlitMaterial = .{ .albedo = .{ 1.0, 0.2, 0.2, 1.0 } }; + const camera: Camera = .{ + .projection = .perspective(std.math.degreesToRadians(90), 800.0 / 600.0, 0.01, 1000), + .view = .lookAt(.new(.{ 1, 1, -2 }), .new(.{ 0, 0, 1 }), .new(.{ 0, 1, 0 })), + }; + const model: Model = .{ + .matrix = math.Mat4x4.translate( + 0, + 0, + 0, + ) + .mul(math.Quat.fromEuler(.init(0, 0, 0)).toMat4()) + .mul(math.Mat4x4.scale( + 1, + 1, + 1, + )), + }; + + // + // SDL + // + + // --- # --- + if (!sdl.SDL_Init(sdl.SDL_INIT_VIDEO)) return error.FailedToInitSDL; + defer sdl.SDL_Quit(); + + // --- # --- + const window = sdl.SDL_CreateWindow("NYXGFX", 800, 600, 0) orelse return error.FailedTomakeWindow; + defer sdl.SDL_DestroyWindow(window); + + // --- # --- + const props = sdl.SDL_GetWindowProperties(window); + const display = sdl.SDL_GetPointerProperty(props, sdl.SDL_PROP_WINDOW_X11_DISPLAY_POINTER, null); + const xwindow = sdl.SDL_GetNumberProperty(props, sdl.SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); + + // + // GFX + // + + // --- # --- + var gfx: Gfx = try .init(.{ + .window = .{ + .xlib = .{ + .window = @intCast(xwindow), + .display = display.?, + }, + }, + .backend = .vulkan, + }, init.gpa, init.io); + defer gfx.deinit(); + + errdefer gfx.deinit(); + + // --- # --- + const adapter = try gfx.makeAdapter(.{ .gpu_type = .any }); + const surface = try gfx.makeSurface(); + const device = try gfx.makeDevice(adapter, surface); + const swapchain = try gfx.makeSwapchain(adapter, device, surface); + + // --- TEXTURES --- + const texture = try gfx.makeTexture(.{ + .image = .{ .path = "examples/assets/textures/texture.jpg", .vertical_flip = true }, + .sampler = .{ .min_filter = .linear, .address_u = .repeat }, + }, adapter, device); + + // --- BUFFERS --- + const vbuf = try gfx.makeBuffer(vertices, .vertex, adapter, device); + const ibuf = try gfx.makeBuffer(indices, .index, adapter, device); + + // --- SHADERS --- + const simple_vert = try gfx.makeShader("examples/assets/shaders/compiled/3D.vert.spv", device); + const simple_frag = try gfx.makeShader("examples/assets/shaders/compiled/3D.frag.spv", device); + + // --- PIPELINE --- + const pipeline = try gfx.makePipeline(.{ + .vertex_attributes = &.{ + .{ .location = 0, .format = .vec3, .offset = @offsetOf(Vertex, "pos") }, + .{ .location = 1, .format = .vec3, .offset = @offsetOf(Vertex, "normal") }, + .{ .location = 2, .format = .vec2, .offset = @offsetOf(Vertex, "uv") }, + }, + .vertex_binding = .{ .stride = @sizeOf(Vertex) }, + + .uniforms = &.{ + .{ .name = "camera", .size = @sizeOf(Camera) }, + .{ .name = "model", .size = @sizeOf(Model) }, + }, + + .textures = &.{ + texture, + }, + + .depth_format = .d32_sfloat, + + .vert_shader = simple_vert, + .frag_shader = simple_frag, + + .device = device, + .swapchain = swapchain, + }); + + // --- MISC --- + var event: sdl.SDL_Event = undefined; + var running: bool = true; + var i: f32 = 0; + + while (running) { + // --- INPUTS --- + while (sdl.SDL_PollEvent(&event)) { + switch (event.type) { + sdl.SDL_EVENT_QUIT => running = false, + else => {}, + } + } + + // --- ANIMATION --- + i += 0.001; + i = @mod(i, 1); + material.albedo[1] = i; + + try pipeline.setUniform("camera", Camera{ + .projection = camera.projection.transpose(), + .view = camera.view.transpose(), + }); + try pipeline.setUniform("model", Model{ + .matrix = model.matrix.transpose(), + }); + + // --- FRAME --- + try gfx.beginFrame(swapchain, device); + defer gfx.endFrame(swapchain, device) catch unreachable; + + // --- NEW PASS --- + if (gfx.beginPass(swapchain, .{ + .clear_color = .{ 0.01, 0.01, 0.01, 1.0 }, + .depth_enabled = true, + })) |pass| { + defer pass.end(); + + try vbuf.bind(); + try ibuf.bind(); + + try pipeline.bind(); + pass.drawIndexed(indices.len, 1, 0, 0, 0); + } + + // --- NEW PASS --- + if (gfx.beginPass(swapchain, .{ + .load_op = .load, + })) |pass| { + defer pass.end(); + } + } +} diff --git a/src/gfx.zig b/src/gfx.zig index 342ee73..29b860b 100644 --- a/src/gfx.zig +++ b/src/gfx.zig @@ -7,32 +7,34 @@ const std = @import("std"); const gtl = @import("gtl"); const vk = @import("vulkan"); const common = @import("rhi/common.zig"); -const Handle = gtl.Handle; const Self = @This(); +// --- # --- +pub const Handle = gtl.Handle; + // --- CONFIGS --- -const AdapterConfig = common.AdapterConfig; -const RenderPassConfig = common.RenderPassConfig; -const PipelineConfig = common.PipelineConfig; -const BufferUsage = common.BufferUsage; -const Pass = common.Pass; -const ImageConfig = common.ImageConfig; -const SamplerConfig = common.SamplerConfig; -const TextureConfig = common.TextureConfig; +pub const AdapterConfig = common.AdapterConfig; +pub const RenderPassConfig = common.RenderPassConfig; +pub const PipelineConfig = common.PipelineConfig; +pub const BufferUsage = common.BufferUsage; +pub const Pass = common.Pass; +pub const ImageConfig = common.ImageConfig; +pub const SamplerConfig = common.SamplerConfig; +pub const TextureConfig = common.TextureConfig; // --- HANDLES --- -const Window = common.Window; -const Adapter = common.Adapter; -const Device = common.Device; -const Buffer = common.Buffer; -const Surface = common.Surface; -const Swapchain = common.Swapchain; -const Image = common.Image; -const Sampler = common.Sampler; -const Texture = common.Texture; +pub const Window = common.Window; +pub const Adapter = common.Adapter; +pub const Device = common.Device; +pub const Buffer = common.Buffer; +pub const Surface = common.Surface; +pub const Swapchain = common.Swapchain; +pub const Image = common.Image; +pub const Sampler = common.Sampler; +pub const Texture = common.Texture; -const Shader = common.Shader; -const Pipeline = common.Pipeline; +pub const Shader = common.Shader; +pub const Pipeline = common.Pipeline; // --- RHI --- const VulkanRHI = @import("rhi/vulkan/vulkan.zig"); @@ -288,7 +290,7 @@ pub fn beginPass( config: RenderPassConfig, ) ?Pass { switch (self.ctx) { - .vulkan => |*v| v.beginRendering(swapchain, config) catch return null, + .vulkan => |*v| v.beginPass(swapchain, config) catch return null, } return .{ diff --git a/src/rhi/common.zig b/src/rhi/common.zig index 0e29260..c9135f5 100644 --- a/src/rhi/common.zig +++ b/src/rhi/common.zig @@ -93,10 +93,30 @@ pub const VertexAttribute = struct { /// ---------------------------------------------------- pub const UniformDesc = struct { name: []const u8, - offset: u32, size: u32, }; +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const CompareOp = enum { + never, + less, + equal, + less_equal, + greater, + not_equal, + greater_equal, + always, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const DepthFormat = enum { + d16_unorm, + d24_unorm_s8, + d32_sfloat, +}; + /// ---------------------------------------------------- /// ---------------------------------------------------- pub const PipelineConfig = struct { @@ -127,12 +147,17 @@ pub const PipelineConfig = struct { front_face: enum { ccw, cw, - } = .cw, + } = .ccw, blend_mode: enum { none, alpha, } = .none, + + depth_format: ?DepthFormat = null, + depth_test: bool = true, + depth_write: bool = true, + depth_compare: CompareOp = .less, }; /// ---------------------------------------------------- @@ -145,7 +170,7 @@ pub const Pass = struct { /// ---------------------------------------------------- pub fn end(self: *const Pass) void { switch (self.gfx.ctx) { - .vulkan => |*v| v.endRendering(self.swapchain), + .vulkan => |*v| v.endPass(self.swapchain), } } @@ -185,8 +210,18 @@ pub const StoreOp = enum { /// ---------------------------------------------------- pub const RenderPassConfig = struct { clear_color: [4]f32 = .{ 0.0, 0.0, 0.0, 1.0 }, + + depth_enabled: bool = false, + depth_format: DepthFormat = .d32_sfloat, + depth_load_op: LoadOp = .clear, + depth_store_op: StoreOp = .dont_care, + + clear_depth: f32 = 1.0, + clear_stencil: u8 = 0, + load_op: LoadOp = .clear, store_op: StoreOp = .store, + width: ?u32 = null, height: ?u32 = null, }; @@ -244,6 +279,7 @@ pub const AddressMode = enum { pub const ImageUsage = enum { texture, render_target, + depth, }; /// ---------------------------------------------------- @@ -258,6 +294,8 @@ pub const ImageConfig = struct { /// Required for `.data`. `null` = use source dimensions size: ?[2]u32 = null, + vertical_flip: bool = false, + format: PixelFormat = .rgba8_srgb, usage: ImageUsage = .texture, mip_levels: u32 = 1, diff --git a/src/rhi/vulkan/resource/image.zig b/src/rhi/vulkan/resource/image.zig index ad70500..f31c59e 100644 --- a/src/rhi/vulkan/resource/image.zig +++ b/src/rhi/vulkan/resource/image.zig @@ -50,6 +50,7 @@ pub fn init( if (config.path) |path| { var tex_channel: i32 = 0; + stb.stbi_set_flip_vertically_on_load(@intFromBool(config.vertical_flip)); const data = stb.stbi_load(path.ptr, &tex_size[0], &tex_size[1], &tex_channel, stb.STBI_rgb_alpha) orelse { return error.FileNotFound; }; @@ -86,6 +87,7 @@ pub fn init( switch (config.usage) { .texture => {}, .render_target => usage |= vk.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .depth => usage |= vk.VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, } const image_info: vk.VkImageCreateInfo = .{ diff --git a/src/rhi/vulkan/resource/pipeline.zig b/src/rhi/vulkan/resource/pipeline.zig index 05b381e..dedf6d6 100644 --- a/src/rhi/vulkan/resource/pipeline.zig +++ b/src/rhi/vulkan/resource/pipeline.zig @@ -34,6 +34,7 @@ descriptor_sets: [MAX_FRAMES_IN_FLIGHT]vk.VkDescriptorSet = .{null} ** MAX_FRAME uniform_buffers: [MAX_FRAMES_IN_FLIGHT]vk.VkBuffer = .{null} ** MAX_FRAMES_IN_FLIGHT, uniform_memories: [MAX_FRAMES_IN_FLIGHT]vk.VkDeviceMemory = .{null} ** MAX_FRAMES_IN_FLIGHT, uniforms: []UniformDesc, +uniform_offsets: []u32, vulkan: *VulkanRHI, @@ -51,20 +52,37 @@ pub fn init( var self: Self = .{ .device = device, .uniforms = &.{}, + .uniform_offsets = &.{}, .vulkan = vulkan, .alloc = alloc, }; errdefer self.deinit(); // --- COMPUTE UNIFORM LAYOUT --- + // Each uniform block gets its own descriptor binding. Blocks are packed + // into a single per-frame buffer, each at a `minUniformBufferOffsetAlignment` + // aligned offset so the same layout maps to `glBindBufferRange` (GL) and + // CBV offsets (D3D12) unchanged. var uniform_size: u32 = 0; - for (config.uniforms) |u| { - uniform_size = @max(uniform_size, u.offset + u.size); - } - uniform_size = std.mem.alignForward(u32, uniform_size, 16); - - // --- COPY UNIFORMS --- if (config.uniforms.len > 0) { + var props: vk.VkPhysicalDeviceProperties = .{}; + vk.vkGetPhysicalDeviceProperties(device.physical_device, &props); + + const alignment: u32 = @intCast(@max( + @as(u64, 16), + props.limits.minUniformBufferOffsetAlignment, + )); + + const uniform_offsets = try alloc.alloc(u32, config.uniforms.len); + self.uniform_offsets = uniform_offsets; + + for (config.uniforms, 0..) |u, i| { + uniform_offsets[i] = std.mem.alignForward(u32, uniform_size, alignment); + uniform_size = uniform_offsets[i] + u.size; + } + uniform_size = std.mem.alignForward(u32, uniform_size, alignment); + + // --- COPY UNIFORMS --- const uniforms = try alloc.alloc(UniformDesc, config.uniforms.len); @memcpy(uniforms, config.uniforms); self.uniforms = uniforms; @@ -83,9 +101,9 @@ pub fn init( var bindings: [MAX_BINDINGS]vk.VkDescriptorSetLayoutBinding = undefined; var binding_count: u32 = 0; - if (uniform_size > 0) { + for (config.uniforms, 0..) |_, i| { bindings[binding_count] = .{ - .binding = 0, + .binding = @intCast(i), .descriptorType = vk.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .stageFlags = vk.VK_SHADER_STAGE_VERTEX_BIT | vk.VK_SHADER_STAGE_FRAGMENT_BIT, @@ -94,9 +112,9 @@ pub fn init( binding_count += 1; } - for (0..texture_count) |i| { + for (config.textures, 0..) |_, i| { bindings[binding_count] = .{ - .binding = @intCast(i + 1), + .binding = @intCast(config.uniforms.len + i), .descriptorType = vk.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .stageFlags = vk.VK_SHADER_STAGE_FRAGMENT_BIT, @@ -298,10 +316,29 @@ pub fn init( .pAttachments = &color_blend_attachment, }; + var depth_stencil: vk.VkPipelineDepthStencilStateCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .depthTestEnable = if (config.depth_test) vk.VK_TRUE else vk.VK_FALSE, + .depthWriteEnable = if (config.depth_write) vk.VK_TRUE else vk.VK_FALSE, + .depthCompareOp = switch (config.depth_compare) { + .never => vk.VK_COMPARE_OP_NEVER, + .less => vk.VK_COMPARE_OP_LESS, + .equal => vk.VK_COMPARE_OP_EQUAL, + .less_equal => vk.VK_COMPARE_OP_LESS_OR_EQUAL, + .greater => vk.VK_COMPARE_OP_GREATER, + .not_equal => vk.VK_COMPARE_OP_NOT_EQUAL, + .greater_equal => vk.VK_COMPARE_OP_GREATER_OR_EQUAL, + .always => vk.VK_COMPARE_OP_ALWAYS, + }, + .depthBoundsTestEnable = vk.VK_FALSE, + .stencilTestEnable = vk.VK_FALSE, + }; + var rendering_info: vk.VkPipelineRenderingCreateInfo = .{ .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO, .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapchain.format, + .depthAttachmentFormat = if (config.depth_format) |f| VulkanRHI.depthFormatToVk(f) else vk.VK_FORMAT_UNDEFINED, }; var pipeline_info: vk.VkGraphicsPipelineCreateInfo = .{ @@ -316,6 +353,7 @@ pub fn init( .pRasterizationState = &rasterizer, .pMultisampleState = &multisampling, .pColorBlendState = &color_blending, + .pDepthStencilState = if (config.depth_format != null) &depth_stencil else null, .pDynamicState = &dynamic_state, .layout = pipeline_layout, @@ -347,10 +385,10 @@ pub fn init( var pool_sizes: [2]vk.VkDescriptorPoolSize = undefined; var pool_size_count: u32 = 0; - if (uniform_size > 0) { + if (config.uniforms.len > 0) { pool_sizes[pool_size_count] = .{ .type = vk.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .descriptorCount = @intCast(MAX_FRAMES_IN_FLIGHT), + .descriptorCount = @intCast(MAX_FRAMES_IN_FLIGHT * config.uniforms.len), }; pool_size_count += 1; } @@ -440,24 +478,24 @@ pub fn init( // --- WRITE DESCRIPTOR SET --- var writes: [MAX_WRITES]vk.VkWriteDescriptorSet = undefined; var write_count: u32 = 0; - var buffer_descriptor: vk.VkDescriptorBufferInfo = undefined; + var buffer_descriptors: [MAX_BINDINGS]vk.VkDescriptorBufferInfo = undefined; var image_descriptors: [MAX_TEXTURES]vk.VkDescriptorImageInfo = undefined; - if (uniform_size > 0) { - buffer_descriptor = .{ + for (config.uniforms, 0..) |u, i| { + buffer_descriptors[i] = .{ .buffer = self.uniform_buffers[frame], - .offset = 0, - .range = uniform_size, + .offset = self.uniform_offsets[i], + .range = u.size, }; writes[write_count] = .{ .sType = vk.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = self.descriptor_sets[frame], - .dstBinding = 0, + .dstBinding = @intCast(i), .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .pBufferInfo = &buffer_descriptor, + .pBufferInfo = &buffer_descriptors[i], }; write_count += 1; } @@ -472,7 +510,7 @@ pub fn init( writes[write_count] = .{ .sType = vk.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = self.descriptor_sets[frame], - .dstBinding = @intCast(i + 1), + .dstBinding = @intCast(config.uniforms.len + i), .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, @@ -514,6 +552,7 @@ pub fn deinit(self: *Self) void { vk.vkDestroyPipeline(self.device.raw, raw, null); } self.alloc.free(self.uniforms); + self.alloc.free(self.uniform_offsets); } /// ---------------------------------------------------- @@ -521,7 +560,7 @@ pub fn deinit(self: *Self) void { /// ---------------------------------------------------- pub fn setUniform(self: *Self, name: []const u8, value: anytype) !void { const uniform_memory = self.uniform_memories[self.vulkan.current_frame]; - for (self.uniforms) |uniform| { + for (self.uniforms, 0..) |uniform, i| { if (std.mem.eql(u8, uniform.name, name)) { if (@sizeOf(@TypeOf(value)) > uniform.size) { return error.UniformSizeMismatch; @@ -531,7 +570,7 @@ pub fn setUniform(self: *Self, name: []const u8, value: anytype) !void { if (vk.vkMapMemory( self.device.raw, uniform_memory, - uniform.offset, + self.uniform_offsets[i], uniform.size, 0, &mapped, diff --git a/src/rhi/vulkan/resource/shader.zig b/src/rhi/vulkan/resource/shader.zig index 1a5a354..8ac88bd 100644 --- a/src/rhi/vulkan/resource/shader.zig +++ b/src/rhi/vulkan/resource/shader.zig @@ -12,7 +12,7 @@ const Self = @This(); // FIELDS // -raw: *vk.VkShaderModule_T, +raw: vk.VkShaderModule, device: *const Device, /// ---------------------------------------------------- @@ -40,7 +40,10 @@ pub fn init( /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn deinit(self: *Self) void { - vk.vkDestroyShaderModule(self.device.raw, self.raw, null); + if (self.raw) |raw| { + vk.vkDestroyShaderModule(self.device.raw, raw, null); + self.raw = null; + } } /// ---------------------------------------------------- diff --git a/src/rhi/vulkan/resource/swapchain.zig b/src/rhi/vulkan/resource/swapchain.zig index 48f4e75..45c00c4 100644 --- a/src/rhi/vulkan/resource/swapchain.zig +++ b/src/rhi/vulkan/resource/swapchain.zig @@ -27,6 +27,11 @@ raw: *vk.VkSwapchainKHR_T, images: []vk.VkImage, image_views: []vk.VkImageView, +depth_images: []vk.VkImage = &.{}, +depth_memories: []vk.VkDeviceMemory = &.{}, +depth_views: []vk.VkImageView = &.{}, +depth_format: vk.VkFormat = vk.VK_FORMAT_UNDEFINED, + format: vk.VkFormat, extent: vk.VkExtent2D, @@ -158,6 +163,8 @@ pub fn init( /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn deinit(self: *Self) void { + self.destroyDepth(); + for (self.image_views) |view| { vk.vkDestroyImageView(self.device.raw, view, null); } @@ -167,3 +174,164 @@ pub fn deinit(self: *Self) void { self.alloc.free(self.images); self.alloc.free(self.image_views); } + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn ensureDepth(self: *Self, format: vk.VkFormat) !void { + if (self.depth_format == format and self.depth_views.len > 0) return; + + self.destroyDepth(); + + const count = self.images.len; + const images = try self.alloc.alloc(vk.VkImage, count); + errdefer self.alloc.free(images); + const memories = try self.alloc.alloc(vk.VkDeviceMemory, count); + errdefer self.alloc.free(memories); + const views = try self.alloc.alloc(vk.VkImageView, count); + + var created: usize = 0; + errdefer { + for (0..created) |i| { + if (views[i]) |view| vk.vkDestroyImageView(self.device.raw, view, null); + if (memories[i]) |memory| vk.vkFreeMemory(self.device.raw, memory, null); + if (images[i]) |image| vk.vkDestroyImage(self.device.raw, image, null); + } + self.alloc.free(views); + } + + const aspect: vk.VkImageAspectFlags = if (isDepthStencilFormat(format)) + vk.VK_IMAGE_ASPECT_DEPTH_BIT | vk.VK_IMAGE_ASPECT_STENCIL_BIT + else + vk.VK_IMAGE_ASPECT_DEPTH_BIT; + + const image_info: vk.VkImageCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = vk.VK_IMAGE_TYPE_2D, + .extent = .{ + .width = self.extent.width, + .height = self.extent.height, + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .format = format, + .tiling = vk.VK_IMAGE_TILING_OPTIMAL, + .initialLayout = vk.VK_IMAGE_LAYOUT_UNDEFINED, + .usage = vk.VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + .samples = vk.VK_SAMPLE_COUNT_1_BIT, + .sharingMode = vk.VK_SHARING_MODE_EXCLUSIVE, + }; + + for (0..count) |i| { + created = i; + + if (vk.vkCreateImage(self.device.raw, &image_info, null, &images[i]) != vk.VK_SUCCESS) { + return error.FailedToCreateDepthImage; + } + + var mem_req: vk.VkMemoryRequirements = .{}; + vk.vkGetImageMemoryRequirements(self.device.raw, images[i], &mem_req); + + const mem_type = findMemoryType( + self.device.physical_device, + mem_req.memoryTypeBits, + vk.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + ) orelse return error.FailedToFindSuitableMemoryType; + + const alloc_info: vk.VkMemoryAllocateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = mem_req.size, + .memoryTypeIndex = mem_type, + }; + if (vk.vkAllocateMemory(self.device.raw, &alloc_info, null, &memories[i]) != vk.VK_SUCCESS) { + return error.FailedToAllocateDepthMemory; + } + _ = vk.vkBindImageMemory(self.device.raw, images[i], memories[i], 0); + + const view_info: vk.VkImageViewCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = images[i], + .viewType = vk.VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components = .{ + .r = vk.VK_COMPONENT_SWIZZLE_IDENTITY, + .g = vk.VK_COMPONENT_SWIZZLE_IDENTITY, + .b = vk.VK_COMPONENT_SWIZZLE_IDENTITY, + .a = vk.VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = .{ + .aspectMask = aspect, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + if (vk.vkCreateImageView(self.device.raw, &view_info, null, &views[i]) != vk.VK_SUCCESS) { + return error.FailedToCreateDepthImageView; + } + + created = i + 1; + } + + self.depth_images = images; + self.depth_memories = memories; + self.depth_views = views; + self.depth_format = format; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +fn destroyDepth(self: *Self) void { + if (self.depth_views.len > 0) { + for (self.depth_views) |view| { + if (view) |v| vk.vkDestroyImageView(self.device.raw, v, null); + } + self.alloc.free(self.depth_views); + self.depth_views = &.{}; + } + if (self.depth_memories.len > 0) { + for (self.depth_memories) |memory| { + if (memory) |m| vk.vkFreeMemory(self.device.raw, m, null); + } + self.alloc.free(self.depth_memories); + self.depth_memories = &.{}; + } + if (self.depth_images.len > 0) { + for (self.depth_images) |image| { + if (image) |im| vk.vkDestroyImage(self.device.raw, im, null); + } + self.alloc.free(self.depth_images); + self.depth_images = &.{}; + } + self.depth_format = vk.VK_FORMAT_UNDEFINED; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +fn isDepthStencilFormat(format: vk.VkFormat) bool { + return format == vk.VK_FORMAT_D16_UNORM_S8_UINT or + format == vk.VK_FORMAT_D24_UNORM_S8_UINT or + format == vk.VK_FORMAT_D32_SFLOAT_S8_UINT; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +fn findMemoryType( + physical_device: vk.VkPhysicalDevice, + type_bits: u32, + properties: vk.VkMemoryPropertyFlags, +) ?u32 { + var mem_props: vk.VkPhysicalDeviceMemoryProperties = .{}; + vk.vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_props); + + for (0..mem_props.memoryTypeCount) |i| { + if ((type_bits & (@as(u32, 1) << @intCast(i))) != 0 and + (mem_props.memoryTypes[i].propertyFlags & properties) == properties) + { + return @intCast(i); + } + } + + return null; +} diff --git a/src/rhi/vulkan/vulkan.zig b/src/rhi/vulkan/vulkan.zig index 97ec9b7..4461990 100644 --- a/src/rhi/vulkan/vulkan.zig +++ b/src/rhi/vulkan/vulkan.zig @@ -1,5 +1,6 @@ //! ---------------------------------------------------- -//! `🗲` Vulkan RHI `🗲` +//! `🗲` Vulkan RHI `🗲` +//! 😭 Send Help 😭 //! ---------------------------------------------------- const std = @import("std"); @@ -44,6 +45,16 @@ const Texture = common.Texture; // --- # --- pub const MAX_FRAMES_IN_FLIGHT: usize = 3; +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn depthFormatToVk(format: common.DepthFormat) vk.VkFormat { + return switch (format) { + .d16_unorm => vk.VK_FORMAT_D16_UNORM, + .d24_unorm_s8 => vk.VK_FORMAT_D24_UNORM_S8_UINT, + .d32_sfloat => vk.VK_FORMAT_D32_SFLOAT, + }; +} + /// ---------------------------------------------------- /// ---------------------------------------------------- const Frame = struct { @@ -419,7 +430,7 @@ pub fn endFrame( /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn beginRendering( +pub fn beginPass( self: *Self, swapchain: Handle(Swapchain), config: RenderPassConfig, @@ -474,6 +485,79 @@ pub fn beginRendering( &barrier, ); + var depth_attachment: vk.VkRenderingAttachmentInfo = undefined; + if (config.depth_enabled) { + const depth_format = depthFormatToVk(config.depth_format); + try raw_swapchain.ensureDepth(depth_format); + + const depth_aspect: vk.VkImageAspectFlags = if (config.depth_format == .d24_unorm_s8) + vk.VK_IMAGE_ASPECT_DEPTH_BIT | vk.VK_IMAGE_ASPECT_STENCIL_BIT + else + vk.VK_IMAGE_ASPECT_DEPTH_BIT; + + const depth_barrier: vk.VkImageMemoryBarrier = .{ + .sType = vk.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + + .srcAccessMask = 0, + .dstAccessMask = vk.VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + + .oldLayout = vk.VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = vk.VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + + .srcQueueFamilyIndex = vk.VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = vk.VK_QUEUE_FAMILY_IGNORED, + + .image = raw_swapchain.depth_images[frame.image_index], + + .subresourceRange = .{ + .aspectMask = depth_aspect, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + vk.vkCmdPipelineBarrier( + frame.cmd_buf, + + vk.VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + vk.VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | vk.VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + + 0, + + 0, + null, + + 0, + null, + + 1, + &depth_barrier, + ); + + depth_attachment = .{ + .sType = vk.VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, + .imageView = raw_swapchain.depth_views[frame.image_index], + .imageLayout = vk.VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + .loadOp = switch (config.depth_load_op) { + .clear => vk.VK_ATTACHMENT_LOAD_OP_CLEAR, + .load => vk.VK_ATTACHMENT_LOAD_OP_LOAD, + .dont_care => vk.VK_ATTACHMENT_LOAD_OP_DONT_CARE, + }, + .storeOp = switch (config.depth_store_op) { + .store => vk.VK_ATTACHMENT_STORE_OP_STORE, + .dont_care => vk.VK_ATTACHMENT_STORE_OP_DONT_CARE, + }, + .clearValue = .{ + .depthStencil = .{ + .depth = config.clear_depth, + .stencil = @intCast(config.clear_stencil), + }, + }, + }; + } + const color_attachment: vk.VkRenderingAttachmentInfo = .{ .sType = vk.VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, .imageView = raw_swapchain.image_views[frame.image_index], @@ -494,7 +578,7 @@ pub fn beginRendering( }, }; - const rendering_info: vk.VkRenderingInfo = .{ + var rendering_info: vk.VkRenderingInfo = .{ .sType = vk.VK_STRUCTURE_TYPE_RENDERING_INFO, .renderArea = .{ .offset = .{ @@ -507,6 +591,9 @@ pub fn beginRendering( .colorAttachmentCount = 1, .pColorAttachments = &color_attachment, }; + if (config.depth_enabled) { + rendering_info.pDepthAttachment = &depth_attachment; + } vk.vkCmdBeginRendering( frame.cmd_buf, @@ -536,7 +623,7 @@ pub fn beginRendering( /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn endRendering( +pub fn endPass( self: *Self, swapchain: Handle(Swapchain), ) void { diff --git a/src/vendor/fast_obj/fast_obj.c b/src/vendor/fast_obj/fast_obj.c new file mode 100644 index 0000000..2164802 --- /dev/null +++ b/src/vendor/fast_obj/fast_obj.c @@ -0,0 +1,28 @@ +/* + * + * MIT License + * + * Copyright (c) 2018 Richard Knight + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#define FAST_OBJ_IMPLEMENTATION +#include "fast_obj.h" diff --git a/src/vendor/fast_obj/fast_obj.h b/src/vendor/fast_obj/fast_obj.h new file mode 100644 index 0000000..3a05403 --- /dev/null +++ b/src/vendor/fast_obj/fast_obj.h @@ -0,0 +1,1357 @@ +/* + * fast_obj + * + * Version 1.3 + * + * MIT License + * + * Copyright (c) 2018-2021 Richard Knight + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#ifndef FAST_OBJ_HDR +#define FAST_OBJ_HDR + +#define FAST_OBJ_VERSION_MAJOR 1 +#define FAST_OBJ_VERSION_MINOR 3 +#define FAST_OBJ_VERSION \ + ((FAST_OBJ_VERSION_MAJOR << 8) | FAST_OBJ_VERSION_MINOR) + +#include + +typedef struct { + /* Texture name from .mtl file */ + char *name; + + /* Resolved path to texture */ + char *path; + +} fastObjTexture; + +typedef struct { + /* Material name */ + char *name; + + /* Parameters */ + float Ka[3]; /* Ambient */ + float Kd[3]; /* Diffuse */ + float Ks[3]; /* Specular */ + float Ke[3]; /* Emission */ + float Kt[3]; /* Transmittance */ + float Ns; /* Shininess */ + float Ni; /* Index of refraction */ + float Tf[3]; /* Transmission filter */ + float d; /* Disolve (alpha) */ + int illum; /* Illumination model */ + + /* Set for materials that don't come from the associated mtllib */ + int fallback; + + /* Texture map indices in fastObjMesh textures array */ + unsigned int map_Ka; + unsigned int map_Kd; + unsigned int map_Ks; + unsigned int map_Ke; + unsigned int map_Kt; + unsigned int map_Ns; + unsigned int map_Ni; + unsigned int map_d; + unsigned int map_bump; + +} fastObjMaterial; + +/* Allows user override to bigger indexable array */ +#ifndef FAST_OBJ_UINT_TYPE +#define FAST_OBJ_UINT_TYPE unsigned int +#endif + +typedef FAST_OBJ_UINT_TYPE fastObjUInt; + +typedef struct { + fastObjUInt p; + fastObjUInt t; + fastObjUInt n; + +} fastObjIndex; + +typedef struct { + /* Group name */ + char *name; + + /* Number of faces */ + unsigned int face_count; + + /* First face in fastObjMesh face_* arrays */ + unsigned int face_offset; + + /* First index in fastObjMesh indices array */ + unsigned int index_offset; + +} fastObjGroup; + +/* Note: a dummy zero-initialized value is added to the first index + of the positions, texcoords, normals and textures arrays. Hence, + valid indices into these arrays start from 1, with an index of 0 + indicating that the attribute is not present. */ +typedef struct { + /* Vertex data */ + unsigned int position_count; + float *positions; + + unsigned int texcoord_count; + float *texcoords; + + unsigned int normal_count; + float *normals; + + unsigned int color_count; + float *colors; + + /* Face data: one element for each face */ + unsigned int face_count; + unsigned int *face_vertices; + unsigned int *face_materials; + unsigned char *face_lines; + + /* Index data: one element for each face vertex */ + unsigned int index_count; + fastObjIndex *indices; + + /* Materials */ + unsigned int material_count; + fastObjMaterial *materials; + + /* Texture maps */ + unsigned int texture_count; + fastObjTexture *textures; + + /* Mesh objects ('o' tag in .obj file) */ + unsigned int object_count; + fastObjGroup *objects; + + /* Mesh groups ('g' tag in .obj file) */ + unsigned int group_count; + fastObjGroup *groups; + +} fastObjMesh; + +typedef struct { + void *(*file_open)(const char *path, void *user_data); + void (*file_close)(void *file, void *user_data); + size_t (*file_read)(void *file, void *dst, size_t bytes, void *user_data); + unsigned long (*file_size)(void *file, void *user_data); +} fastObjCallbacks; + +#ifdef __cplusplus +extern "C" { +#endif + +fastObjMesh *fast_obj_read(const char *path); +fastObjMesh *fast_obj_read_with_callbacks(const char *path, + const fastObjCallbacks *callbacks, + void *user_data); +void fast_obj_destroy(fastObjMesh *mesh); + +#ifdef __cplusplus +} +#endif + +#endif + +#ifdef FAST_OBJ_IMPLEMENTATION + +#include +#include + +#ifndef FAST_OBJ_REALLOC +#define FAST_OBJ_REALLOC realloc +#endif + +#ifndef FAST_OBJ_FREE +#define FAST_OBJ_FREE free +#endif + +#ifdef _WIN32 +#define FAST_OBJ_SEPARATOR '\\' +#define FAST_OBJ_OTHER_SEP '/' +#else +#define FAST_OBJ_SEPARATOR '/' +#define FAST_OBJ_OTHER_SEP '\\' +#endif + +/* Size of buffer to read into */ +#define BUFFER_SIZE 65536 + +/* Max supported power when parsing float */ +#define MAX_POWER 20 + +typedef struct { + /* Final mesh */ + fastObjMesh *mesh; + + /* Current object/group */ + fastObjGroup object; + fastObjGroup group; + + /* Current material index */ + unsigned int material; + + /* Current line in file */ + unsigned int line; + + /* Base path for materials/textures */ + char *base; + +} fastObjData; + +static const double POWER_10_POS[MAX_POWER] = { + 1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, + 1.0e7, 1.0e8, 1.0e9, 1.0e10, 1.0e11, 1.0e12, 1.0e13, + 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18, 1.0e19, +}; + +static const double POWER_10_NEG[MAX_POWER] = { + 1.0e0, 1.0e-1, 1.0e-2, 1.0e-3, 1.0e-4, 1.0e-5, 1.0e-6, + 1.0e-7, 1.0e-8, 1.0e-9, 1.0e-10, 1.0e-11, 1.0e-12, 1.0e-13, + 1.0e-14, 1.0e-15, 1.0e-16, 1.0e-17, 1.0e-18, 1.0e-19, +}; + +static void *memory_realloc(void *ptr, size_t bytes) { + return FAST_OBJ_REALLOC(ptr, bytes); +} + +static void memory_dealloc(void *ptr) { FAST_OBJ_FREE(ptr); } + +#define array_clean(_arr) ((_arr) ? memory_dealloc(_array_header(_arr)), 0 : 0) +#define array_push(_arr, _val) \ + (_array_mgrow(_arr, 1) \ + ? ((_arr)[_array_size(_arr)++] = (_val), _array_size(_arr) - 1) \ + : 0) +#define array_size(_arr) ((_arr) ? _array_size(_arr) : 0) +#define array_capacity(_arr) ((_arr) ? _array_capacity(_arr) : 0) +#define array_empty(_arr) (array_size(_arr) == 0) + +#define _array_header(_arr) ((fastObjUInt *)(_arr) - 2) +#define _array_size(_arr) (_array_header(_arr)[0]) +#define _array_capacity(_arr) (_array_header(_arr)[1]) +#define _array_ngrow(_arr, _n) \ + ((_arr) == 0 || (_array_size(_arr) + (_n) >= _array_capacity(_arr))) +#define _array_mgrow(_arr, _n) \ + (_array_ngrow(_arr, _n) ? (_array_grow(_arr, _n) != 0) : 1) +#define _array_grow(_arr, _n) \ + (*((void **)&(_arr)) = array_realloc(_arr, _n, sizeof(*(_arr)))) + +static void *array_realloc(void *ptr, fastObjUInt n, fastObjUInt b) { + fastObjUInt sz = array_size(ptr); + fastObjUInt nsz = sz + n; + fastObjUInt cap = array_capacity(ptr); + fastObjUInt ncap = cap + cap / 2; + fastObjUInt *r; + + if (ncap < nsz) + ncap = nsz; + ncap = (ncap + 15) & ~15u; + + r = (fastObjUInt *)(memory_realloc(ptr ? _array_header(ptr) : 0, + (size_t)b * ncap + + 2 * sizeof(fastObjUInt))); + if (!r) + return 0; + + r[0] = sz; + r[1] = ncap; + + return (r + 2); +} + +static void *file_open(const char *path, void *user_data) { + (void)(user_data); + return fopen(path, "rb"); +} + +static void file_close(void *file, void *user_data) { + FILE *f; + (void)(user_data); + + f = (FILE *)(file); + fclose(f); +} + +static size_t file_read(void *file, void *dst, size_t bytes, void *user_data) { + FILE *f; + (void)(user_data); + + f = (FILE *)(file); + return fread(dst, 1, bytes, f); +} + +static unsigned long file_size(void *file, void *user_data) { + FILE *f; + long p; + long n; + (void)(user_data); + + f = (FILE *)(file); + + p = ftell(f); + fseek(f, 0, SEEK_END); + n = ftell(f); + fseek(f, p, SEEK_SET); + + if (n > 0) + return (unsigned long)(n); + else + return 0; +} + +static char *string_copy(const char *s, const char *e) { + size_t n; + char *p; + + n = (size_t)(e - s); + p = (char *)(memory_realloc(0, n + 1)); + if (p) { + memcpy(p, s, n); + p[n] = '\0'; + } + + return p; +} + +static char *string_substr(const char *s, size_t a, size_t b) { + return string_copy(s + a, s + b); +} + +static char *string_concat(const char *a, const char *s, const char *e) { + size_t an; + size_t sn; + char *p; + + an = a ? strlen(a) : 0; + sn = (size_t)(e - s); + p = (char *)(memory_realloc(0, an + sn + 1)); + if (p) { + if (a) + memcpy(p, a, an); + memcpy(p + an, s, sn); + p[an + sn] = '\0'; + } + + return p; +} + +static int string_equal(const char *a, const char *s, const char *e) { + size_t an = strlen(a); + size_t sn = (size_t)(e - s); + + return an == sn && memcmp(a, s, an) == 0; +} + +static void string_fix_separators(char *s) { + while (*s) { + if (*s == FAST_OBJ_OTHER_SEP) + *s = FAST_OBJ_SEPARATOR; + s++; + } +} + +static int is_whitespace(char c) { + return (c == ' ' || c == '\t' || c == '\r'); +} + +static int is_newline(char c) { return (c == '\n'); } + +static int is_digit(char c) { return (c >= '0' && c <= '9'); } + +static int is_exponent(char c) { return (c == 'e' || c == 'E'); } + +static const char *skip_name(const char *ptr) { + const char *s = ptr; + + while (!is_newline(*ptr)) + ptr++; + + while (ptr > s && is_whitespace(*(ptr - 1))) + ptr--; + + return ptr; +} + +static const char *skip_whitespace(const char *ptr) { + while (is_whitespace(*ptr)) + ptr++; + + return ptr; +} + +static const char *skip_line(const char *ptr) { + while (!is_newline(*ptr++)) + ; + + return ptr; +} + +static fastObjGroup object_default(void) { + fastObjGroup object; + + object.name = 0; + object.face_count = 0; + object.face_offset = 0; + object.index_offset = 0; + + return object; +} + +static void object_clean(fastObjGroup *object) { memory_dealloc(object->name); } + +static void flush_object(fastObjData *data) { + /* Add object if not empty */ + if (data->object.face_count > 0) + array_push(data->mesh->objects, data->object); + else + object_clean(&data->object); + + /* Reset for more data */ + data->object = object_default(); + data->object.face_offset = array_size(data->mesh->face_vertices); + data->object.index_offset = array_size(data->mesh->indices); +} + +static fastObjGroup group_default(void) { + fastObjGroup group; + + group.name = 0; + group.face_count = 0; + group.face_offset = 0; + group.index_offset = 0; + + return group; +} + +static void group_clean(fastObjGroup *group) { memory_dealloc(group->name); } + +static void flush_group(fastObjData *data) { + /* Add group if not empty */ + if (data->group.face_count > 0) + array_push(data->mesh->groups, data->group); + else + group_clean(&data->group); + + /* Reset for more data */ + data->group = group_default(); + data->group.face_offset = array_size(data->mesh->face_vertices); + data->group.index_offset = array_size(data->mesh->indices); +} + +static const char *parse_int(const char *ptr, int *val) { + int sign; + int num; + + if (*ptr == '-') { + sign = -1; + ptr++; + } else { + sign = +1; + } + + num = 0; + while (is_digit(*ptr)) + num = 10 * num + (*ptr++ - '0'); + + *val = sign * num; + + return ptr; +} + +static const char *parse_float(const char *ptr, float *val) { + double sign; + double num; + double fra; + double div; + unsigned int eval; + const double *powers; + + ptr = skip_whitespace(ptr); + + switch (*ptr) { + case '+': + sign = 1.0; + ptr++; + break; + + case '-': + sign = -1.0; + ptr++; + break; + + default: + sign = 1.0; + break; + } + + num = 0.0; + while (is_digit(*ptr)) + num = 10.0 * num + (double)(*ptr++ - '0'); + + if (*ptr == '.') + ptr++; + + fra = 0.0; + div = 1.0; + + while (is_digit(*ptr)) { + fra = 10.0 * fra + (double)(*ptr++ - '0'); + div *= 10.0; + } + + num += fra / div; + + if (is_exponent(*ptr)) { + ptr++; + + switch (*ptr) { + case '+': + powers = POWER_10_POS; + ptr++; + break; + + case '-': + powers = POWER_10_NEG; + ptr++; + break; + + default: + powers = POWER_10_POS; + break; + } + + eval = 0; + while (is_digit(*ptr)) + eval = 10 * eval + (*ptr++ - '0'); + + num *= (eval >= MAX_POWER) ? 0.0 : powers[eval]; + } + + *val = (float)(sign * num); + + return ptr; +} + +static const char *parse_vertex(fastObjData *data, const char *ptr) { + unsigned int ii; + float v; + + for (ii = 0; ii < 3; ii++) { + ptr = parse_float(ptr, &v); + array_push(data->mesh->positions, v); + } + + ptr = skip_whitespace(ptr); + if (!is_newline(*ptr)) { + /* Fill the colors array until it matches the size of the positions array */ + for (ii = array_size(data->mesh->colors); + ii < array_size(data->mesh->positions) - 3; ++ii) { + array_push(data->mesh->colors, 1.0f); + } + + for (ii = 0; ii < 3; ++ii) { + ptr = parse_float(ptr, &v); + array_push(data->mesh->colors, v); + } + } + + return ptr; +} + +static const char *parse_texcoord(fastObjData *data, const char *ptr) { + unsigned int ii; + float v; + + for (ii = 0; ii < 2; ii++) { + ptr = parse_float(ptr, &v); + array_push(data->mesh->texcoords, v); + } + + return ptr; +} + +static const char *parse_normal(fastObjData *data, const char *ptr) { + unsigned int ii; + float v; + + for (ii = 0; ii < 3; ii++) { + ptr = parse_float(ptr, &v); + array_push(data->mesh->normals, v); + } + + return ptr; +} + +static const char *parse_face(fastObjData *data, const char *ptr, + unsigned char line) { + unsigned int count; + fastObjIndex vn; + int v; + int t; + int n; + + ptr = skip_whitespace(ptr); + + count = 0; + while (!is_newline(*ptr)) { + v = 0; + t = 0; + n = 0; + + ptr = parse_int(ptr, &v); + if (*ptr == '/') { + ptr++; + if (*ptr != '/') + ptr = parse_int(ptr, &t); + + if (*ptr == '/') { + ptr++; + ptr = parse_int(ptr, &n); + } + } + + if (v < 0) + vn.p = (array_size(data->mesh->positions) / 3) - (fastObjUInt)(-v); + else if (v > 0) + vn.p = (fastObjUInt)(v); + else + return ptr; /* Skip lines with no valid vertex index */ + + if (t < 0) + vn.t = (array_size(data->mesh->texcoords) / 2) - (fastObjUInt)(-t); + else if (t > 0) + vn.t = (fastObjUInt)(t); + else + vn.t = 0; + + if (n < 0) + vn.n = (array_size(data->mesh->normals) / 3) - (fastObjUInt)(-n); + else if (n > 0) + vn.n = (fastObjUInt)(n); + else + vn.n = 0; + + array_push(data->mesh->indices, vn); + count++; + + ptr = skip_whitespace(ptr); + } + + array_push(data->mesh->face_vertices, count); + array_push(data->mesh->face_materials, data->material); + + if (line || data->mesh->face_lines) { + /* when line info exists, ensure it uses aligned indexing with other face + * data */ + size_t skipped = array_size(data->mesh->face_vertices) - + array_size(data->mesh->face_lines); + while (--skipped > 0) + array_push(data->mesh->face_lines, 0); + + array_push(data->mesh->face_lines, line); + } + + data->group.face_count++; + data->object.face_count++; + + return ptr; +} + +static const char *parse_object(fastObjData *data, const char *ptr) { + const char *s; + const char *e; + + ptr = skip_whitespace(ptr); + + s = ptr; + ptr = skip_name(ptr); + e = ptr; + + flush_object(data); + data->object.name = string_copy(s, e); + + return ptr; +} + +static const char *parse_group(fastObjData *data, const char *ptr) { + const char *s; + const char *e; + + ptr = skip_whitespace(ptr); + + s = ptr; + ptr = skip_name(ptr); + e = ptr; + + flush_group(data); + data->group.name = string_copy(s, e); + + return ptr; +} + +static fastObjTexture map_default(void) { + fastObjTexture map; + + map.name = 0; + map.path = 0; + + return map; +} + +static fastObjMaterial mtl_default(void) { + fastObjMaterial mtl; + + mtl.name = 0; + + mtl.Ka[0] = 0.0; + mtl.Ka[1] = 0.0; + mtl.Ka[2] = 0.0; + mtl.Kd[0] = 1.0; + mtl.Kd[1] = 1.0; + mtl.Kd[2] = 1.0; + mtl.Ks[0] = 0.0; + mtl.Ks[1] = 0.0; + mtl.Ks[2] = 0.0; + mtl.Ke[0] = 0.0; + mtl.Ke[1] = 0.0; + mtl.Ke[2] = 0.0; + mtl.Kt[0] = 0.0; + mtl.Kt[1] = 0.0; + mtl.Kt[2] = 0.0; + mtl.Ns = 1.0; + mtl.Ni = 1.0; + mtl.Tf[0] = 1.0; + mtl.Tf[1] = 1.0; + mtl.Tf[2] = 1.0; + mtl.d = 1.0; + mtl.illum = 1; + + mtl.fallback = 0; + + mtl.map_Ka = 0; + mtl.map_Kd = 0; + mtl.map_Ks = 0; + mtl.map_Ke = 0; + mtl.map_Kt = 0; + mtl.map_Ns = 0; + mtl.map_Ni = 0; + mtl.map_d = 0; + mtl.map_bump = 0; + + return mtl; +} + +static const char *parse_usemtl(fastObjData *data, const char *ptr) { + const char *s; + const char *e; + unsigned int idx; + fastObjMaterial *mtl; + + ptr = skip_whitespace(ptr); + + /* Parse the material name */ + s = ptr; + ptr = skip_name(ptr); + e = ptr; + + /* Find an existing material with the same name */ + idx = 0; + while (idx < array_size(data->mesh->materials)) { + mtl = &data->mesh->materials[idx]; + if (mtl->name && string_equal(mtl->name, s, e)) + break; + + idx++; + } + + /* If doesn't exist, create a default one with this name + Note: this case happens when OBJ doesn't have its MTL */ + if (idx == array_size(data->mesh->materials)) { + fastObjMaterial new_mtl = mtl_default(); + new_mtl.name = string_copy(s, e); + new_mtl.fallback = 1; + array_push(data->mesh->materials, new_mtl); + } + + data->material = idx; + + return ptr; +} + +static void map_clean(fastObjTexture *map) { + memory_dealloc(map->name); + memory_dealloc(map->path); +} + +static void mtl_clean(fastObjMaterial *mtl) { memory_dealloc(mtl->name); } + +static const char *read_mtl_int(const char *p, int *v) { + return parse_int(p, v); +} + +static const char *read_mtl_single(const char *p, float *v) { + return parse_float(p, v); +} + +static const char *read_mtl_triple(const char *p, float v[3]) { + p = read_mtl_single(p, &v[0]); + p = read_mtl_single(p, &v[1]); + p = read_mtl_single(p, &v[2]); + + return p; +} + +static const char *read_map(fastObjData *data, const char *ptr, + unsigned int *idx) { + const char *s; + const char *e; + fastObjTexture *map; + + ptr = skip_whitespace(ptr); + + /* Don't support options at present */ + if (*ptr == '-') + return ptr; + + /* Read name */ + s = ptr; + ptr = skip_name(ptr); + e = ptr; + + /* Try to find an existing texture map with the same name */ + *idx = 1; /* skip dummy at index 0 */ + while (*idx < array_size(data->mesh->textures)) { + map = &data->mesh->textures[*idx]; + if (map->name && string_equal(map->name, s, e)) + break; + + (*idx)++; + } + + /* Add it to the texture array if it didn't already exist */ + if (*idx == array_size(data->mesh->textures)) { + fastObjTexture new_map = map_default(); + new_map.name = string_copy(s, e); + new_map.path = string_concat(data->base, s, e); + string_fix_separators(new_map.path); + array_push(data->mesh->textures, new_map); + } + + return e; +} + +static int read_mtllib(fastObjData *data, void *file, + const fastObjCallbacks *callbacks, void *user_data) { + unsigned long n; + const char *s; + char *contents; + size_t l; + const char *p; + const char *e; + int found_d; + fastObjMaterial mtl; + + /* Read entire file */ + n = callbacks->file_size(file, user_data); + + contents = (char *)(memory_realloc(0, n + 1)); + if (!contents) + return 0; + + l = callbacks->file_read(file, contents, n, user_data); + contents[l] = '\n'; + + mtl = mtl_default(); + + found_d = 0; + + p = contents; + e = contents + l; + while (p < e) { + p = skip_whitespace(p); + + switch (*p) { + case 'n': + p++; + if (p[0] == 'e' && p[1] == 'w' && p[2] == 'm' && p[3] == 't' && + p[4] == 'l' && is_whitespace(p[5])) { + /* Push previous material (if there is one) */ + if (mtl.name) { + array_push(data->mesh->materials, mtl); + mtl = mtl_default(); + } + + /* Read name */ + p += 5; + + while (is_whitespace(*p)) + p++; + + s = p; + p = skip_name(p); + + mtl.name = string_copy(s, p); + } + break; + + case 'K': + if (p[1] == 'a') + p = read_mtl_triple(p + 2, mtl.Ka); + else if (p[1] == 'd') + p = read_mtl_triple(p + 2, mtl.Kd); + else if (p[1] == 's') + p = read_mtl_triple(p + 2, mtl.Ks); + else if (p[1] == 'e') + p = read_mtl_triple(p + 2, mtl.Ke); + else if (p[1] == 't') + p = read_mtl_triple(p + 2, mtl.Kt); + break; + + case 'N': + if (p[1] == 's') + p = read_mtl_single(p + 2, &mtl.Ns); + else if (p[1] == 'i') + p = read_mtl_single(p + 2, &mtl.Ni); + break; + + case 'T': + if (p[1] == 'r') { + float Tr; + p = read_mtl_single(p + 2, &Tr); + if (!found_d) { + /* Ignore Tr if we've already read d */ + mtl.d = 1.0f - Tr; + } + } else if (p[1] == 'f') + p = read_mtl_triple(p + 2, mtl.Tf); + break; + + case 'd': + if (is_whitespace(p[1])) { + p = read_mtl_single(p + 1, &mtl.d); + found_d = 1; + } + break; + + case 'i': + p++; + if (p[0] == 'l' && p[1] == 'l' && p[2] == 'u' && p[3] == 'm' && + is_whitespace(p[4])) { + p = read_mtl_int(p + 4, &mtl.illum); + } + break; + + case 'm': + p++; + if (p[0] == 'a' && p[1] == 'p' && p[2] == '_') { + p += 3; + if (*p == 'K') { + p++; + if (is_whitespace(p[1])) { + if (*p == 'a') + p = read_map(data, p + 1, &mtl.map_Ka); + else if (*p == 'd') + p = read_map(data, p + 1, &mtl.map_Kd); + else if (*p == 's') + p = read_map(data, p + 1, &mtl.map_Ks); + else if (*p == 'e') + p = read_map(data, p + 1, &mtl.map_Ke); + else if (*p == 't') + p = read_map(data, p + 1, &mtl.map_Kt); + } + } else if (*p == 'N') { + p++; + if (is_whitespace(p[1])) { + if (*p == 's') + p = read_map(data, p + 1, &mtl.map_Ns); + else if (*p == 'i') + p = read_map(data, p + 1, &mtl.map_Ni); + } + } else if (*p == 'd') { + p++; + if (is_whitespace(*p)) + p = read_map(data, p, &mtl.map_d); + } else if ((p[0] == 'b' || p[0] == 'B') && p[1] == 'u' && p[2] == 'm' && + p[3] == 'p' && is_whitespace(p[4])) { + p = read_map(data, p + 4, &mtl.map_bump); + } + } + break; + + case '#': + break; + } + + p = skip_line(p); + } + + /* Push final material */ + if (mtl.name) + array_push(data->mesh->materials, mtl); + + memory_dealloc(contents); + + return 1; +} + +static const char *parse_mtllib(fastObjData *data, const char *ptr, + const fastObjCallbacks *callbacks, + void *user_data) { + const char *s; + const char *e; + char *lib; + void *file; + + ptr = skip_whitespace(ptr); + + s = ptr; + ptr = skip_name(ptr); + e = ptr; + + lib = string_concat(data->base, s, e); + if (lib) { + string_fix_separators(lib); + + file = callbacks->file_open(lib, user_data); + if (file) { + read_mtllib(data, file, callbacks, user_data); + callbacks->file_close(file, user_data); + } + + memory_dealloc(lib); + } + + return ptr; +} + +static void parse_buffer(fastObjData *data, const char *ptr, const char *end, + const fastObjCallbacks *callbacks, void *user_data) { + const char *p; + + p = ptr; + while (p != end) { + p = skip_whitespace(p); + + switch (*p) { + case 'v': + p++; + + switch (*p++) { + case ' ': + case '\t': + p = parse_vertex(data, p); + break; + + case 't': + p = parse_texcoord(data, p); + break; + + case 'n': + p = parse_normal(data, p); + break; + + default: + p--; /* roll p++ back in case *p was a newline */ + } + break; + + case 'f': + p++; + + switch (*p++) { + case ' ': + case '\t': + p = parse_face(data, p, 0); + break; + + default: + p--; /* roll p++ back in case *p was a newline */ + } + break; + + case 'l': + p++; + + switch (*p++) { + case ' ': + case '\t': + p = parse_face(data, p, 1); + break; + + default: + p--; /* roll p++ back in case *p was a newline */ + } + break; + + case 'o': + p++; + + switch (*p++) { + case ' ': + case '\t': + p = parse_object(data, p); + break; + + default: + p--; /* roll p++ back in case *p was a newline */ + } + break; + + case 'g': + p++; + + switch (*p++) { + case ' ': + case '\t': + p = parse_group(data, p); + break; + + default: + p--; /* roll p++ back in case *p was a newline */ + } + break; + + case 'm': + p++; + if (p[0] == 't' && p[1] == 'l' && p[2] == 'l' && p[3] == 'i' && + p[4] == 'b' && is_whitespace(p[5])) + p = parse_mtllib(data, p + 5, callbacks, user_data); + break; + + case 'u': + p++; + if (p[0] == 's' && p[1] == 'e' && p[2] == 'm' && p[3] == 't' && + p[4] == 'l' && is_whitespace(p[5])) + p = parse_usemtl(data, p + 5); + break; + + case '#': + break; + } + + p = skip_line(p); + + data->line++; + } + if (array_size(data->mesh->colors) > 0) { + /* Fill the remaining slots in the colors array */ + unsigned int ii; + for (ii = array_size(data->mesh->colors); + ii < array_size(data->mesh->positions); ++ii) { + array_push(data->mesh->colors, 1.0f); + } + } +} + +void fast_obj_destroy(fastObjMesh *m) { + unsigned int ii; + + for (ii = 0; ii < array_size(m->objects); ii++) + object_clean(&m->objects[ii]); + + for (ii = 0; ii < array_size(m->groups); ii++) + group_clean(&m->groups[ii]); + + for (ii = 0; ii < array_size(m->materials); ii++) + mtl_clean(&m->materials[ii]); + + for (ii = 0; ii < array_size(m->textures); ii++) + map_clean(&m->textures[ii]); + + array_clean(m->positions); + array_clean(m->texcoords); + array_clean(m->normals); + array_clean(m->colors); + array_clean(m->face_vertices); + array_clean(m->face_materials); + array_clean(m->face_lines); + array_clean(m->indices); + array_clean(m->objects); + array_clean(m->groups); + array_clean(m->materials); + array_clean(m->textures); + + memory_dealloc(m); +} + +fastObjMesh *fast_obj_read(const char *path) { + fastObjCallbacks callbacks; + callbacks.file_open = file_open; + callbacks.file_close = file_close; + callbacks.file_read = file_read; + callbacks.file_size = file_size; + + return fast_obj_read_with_callbacks(path, &callbacks, 0); +} + +fastObjMesh *fast_obj_read_with_callbacks(const char *path, + const fastObjCallbacks *callbacks, + void *user_data) { + fastObjData data; + fastObjMesh *m; + void *file; + char *buffer; + char *start; + char *end; + char *last; + fastObjUInt read; + fastObjUInt bytes; + + /* Check if callbacks are valid */ + if (!callbacks) + return 0; + + /* Open file */ + file = callbacks->file_open(path, user_data); + if (!file) + return 0; + + /* Empty mesh */ + m = (fastObjMesh *)(memory_realloc(0, sizeof(fastObjMesh))); + if (!m) + return 0; + + m->positions = 0; + m->texcoords = 0; + m->normals = 0; + m->colors = 0; + m->face_vertices = 0; + m->face_materials = 0; + m->face_lines = 0; + m->indices = 0; + m->materials = 0; + m->textures = 0; + m->objects = 0; + m->groups = 0; + + /* Add dummy position/texcoord/normal/texture */ + array_push(m->positions, 0.0f); + array_push(m->positions, 0.0f); + array_push(m->positions, 0.0f); + + array_push(m->texcoords, 0.0f); + array_push(m->texcoords, 0.0f); + + array_push(m->normals, 0.0f); + array_push(m->normals, 0.0f); + array_push(m->normals, 1.0f); + + array_push(m->textures, map_default()); + + /* Data needed during parsing */ + data.mesh = m; + data.object = object_default(); + data.group = group_default(); + data.material = 0; + data.line = 1; + data.base = 0; + + /* Find base path for materials/textures */ + { + const char *sep1 = strrchr(path, FAST_OBJ_SEPARATOR); + const char *sep2 = strrchr(path, FAST_OBJ_OTHER_SEP); + + /* Use the last separator in the path */ + const char *sep = sep2 && (!sep1 || sep1 < sep2) ? sep2 : sep1; + + if (sep) + data.base = string_substr(path, 0, sep - path + 1); + } + + /* Create buffer for reading file */ + buffer = (char *)(memory_realloc(0, 2 * BUFFER_SIZE * sizeof(char))); + if (!buffer) + return 0; + + start = buffer; + for (;;) { + /* Read another buffer's worth from file */ + read = (fastObjUInt)(callbacks->file_read(file, start, BUFFER_SIZE, + user_data)); + if (read == 0 && start == buffer) + break; + + /* Ensure buffer ends in a newline */ + if (read < BUFFER_SIZE) { + if (read == 0 || start[read - 1] != '\n') + start[read++] = '\n'; + } + + end = start + read; + if (end == buffer) + break; + + /* Find last new line */ + last = end; + while (last > buffer) { + last--; + if (*last == '\n') + break; + } + + /* Check there actually is a new line */ + if (*last != '\n') + break; + + last++; + + /* Process buffer */ + parse_buffer(&data, buffer, last, callbacks, user_data); + + /* Copy overflow for next buffer */ + bytes = (fastObjUInt)(end - last); + memmove(buffer, last, bytes); + start = buffer + bytes; + } + + /* Flush final object/group */ + flush_object(&data); + object_clean(&data.object); + + flush_group(&data); + group_clean(&data.group); + + m->position_count = array_size(m->positions) / 3; + m->texcoord_count = array_size(m->texcoords) / 2; + m->normal_count = array_size(m->normals) / 3; + m->color_count = array_size(m->colors) / 3; + m->face_count = array_size(m->face_vertices); + m->index_count = array_size(m->indices); + m->material_count = array_size(m->materials); + m->texture_count = array_size(m->textures); + m->object_count = array_size(m->objects); + m->group_count = array_size(m->groups); + + /* Clean up */ + memory_dealloc(buffer); + memory_dealloc(data.base); + + callbacks->file_close(file, user_data); + + return m; +} + +#endif diff --git a/zig-out/bin/simple b/zig-out/bin/simple index 404cba6..c60c1b8 100755 Binary files a/zig-out/bin/simple and b/zig-out/bin/simple differ