const std = @import("std"); const mem = std.mem; const Allocator = mem.Allocator; const dg = @import("diligent"); const glm = @import("glm"); const graphics = @import("graphics.zig"); usingnamespace @import("xrvk.zig"); const math = @import("math.zig"); const geometry_source = \\layout(triangles, invocations = 2) in; \\layout(max_vertices = 3, triangle_strip) out; \\ \\struct ViewUBO { \\ mat4 projection; \\ mat4 modelview; \\}; \\ \\layout(binding = 0) uniform Views { \\ ViewUBO views[2]; \\}; \\ \\layout(location = 0) in vec3 in_WorldPos[3]; \\layout(location = 1) in vec3 in_Normal[3]; \\layout(location = 2) in vec2 in_UV0[3]; \\layout(location = 3) in vec2 in_UV1[3]; \\layout(location = 0) out vec3 out_WorldPos; \\layout(location = 1) out vec3 out_Normal; \\layout(location = 2) out vec2 out_UV0; \\layout(location = 3) out vec2 out_UV1; \\ \\void main() { \\ for (int i = 0; i < 3; i++) { \\ vec4 worldPos = views[gl_InvocationID].modelview * gl_in[i].gl_Position; \\ gl_Position = views[gl_InvocationID].projection * worldPos; \\ gl_ViewportIndex = int(gl_InvocationID); \\ out_WorldPos = in_WorldPos[i]; \\ out_Normal = in_Normal[i]; \\ out_UV0 = in_UV0[i]; \\ out_UV1 = in_UV1[i]; \\ EmitVertex(); \\ } \\ EndPrimitive(); \\} ; const CameraAttribs = packed struct { position: glm.Vec4 = glm.Vec4.zeroes(), // camera world position viewport_size: glm.Vec4, // (width, height, 1/width, 1/height) viewport_origin: glm.Vec2 = glm.Vec2.zeroes(), // (min_x, min_y) z_near: f32 = 1, z_far: f32 = 1000, view: glm.Mat4 = glm.Mat4.IDENTITY, proj: glm.Mat4 = glm.Mat4.IDENTITY, viewproj: glm.Mat4 = glm.Mat4.IDENTITY, view_inv: glm.Mat4 = glm.Mat4.IDENTITY, proj_inv: glm.Mat4 = glm.Mat4.IDENTITY, viewproj_inv: glm.Mat4 = glm.Mat4.IDENTITY, extra_data: [5 * 4]f32 = undefined, pub fn init(vp: dg.Viewport, view: glm.Mat4, proj: glm.Mat4) CameraAttribs { var viewproj = view.mul(proj); return .{ .viewport_size = glm.Vec4.init([_]f32{ vp.Width, vp.Height, 1 / vp.Width, 1 / vp.Height }), .view = view, .proj = proj, .viewproj = viewproj, .view_inv = view.invert() orelse unreachable, .proj_inv = proj.invert() orelse unreachable, .viewproj_inv = viewproj.invert() orelse unreachable, }; } pub fn initFake(vp: dg.Viewport, position: glm.Vec4) CameraAttribs { return .{ .position = position, .viewport_size = glm.Vec4.init([_]f32{ vp.Width, vp.Height, 1 / vp.Width, 1 / vp.Height }), }; } }; const CascadeAttribs = packed struct { light_space_scale: glm.Vec4, light_space_scaled_bias: glm.Vec4, start_end_z: glm.Vec4, margin_proj_space: glm.Vec4, }; const ShadowMapAttribs = packed struct { world_to_light: glm.Mat4 = glm.Mat4.IDENTITY, // transform from view space to light projection space cascades: [8]CascadeAttribs = undefined, world_to_shadow_map_uv_depth: [8]glm.Mat4 = undefined, cascade_cam_space_zend: [8]f32 = [_]f32{0} ** 8, shadow_map_dim: glm.Vec4 = undefined, // width, height, 1/width, 1/height num_cascades_i: i32 = 0, num_cascades_f: f32 = 0, visualize_cascades: u32 = 0, visualize_shadowing: u32 = 0, receiver_plane_depth_bias_clamp: f32 = 10, fixed_depth_bias: f32 = 1e-5, cascade_transition_region: f32 = 0.1, max_anisotropy: i32 = 4, vsm_bias: f32 = 1e-4, vsm_light_bleeding_reduction: f32 = 0, evsm_positive_exponent: f32 = 40, evsm_negative_exponent: f32 = 5, is_32bit_evsm: u32 = 1, fixed_filter_size: i32 = 3, filter_world_size: f32 = 0, dummy: f32 = 0, }; const LightAttribs = packed struct { direction: glm.Vec4 = glm.Vec4.init([_]f32{ 0, -1, 0, 0 }), ambient_light: glm.Vec4 = glm.Vec4.init([_]f32{ 0, 0, 0, 0 }), intensity: glm.Vec4 = glm.Vec4.init([_]f32{ 1, 1, 1, 1 }), shadow_attribs: ShadowMapAttribs = .{}, }; const defaultSampler = dg.SamplerDesc{ ._DeviceObjectAttribs = .{ .Name = "Default Sampler" }, .MinFilter = dg.FILTER_TYPE_LINEAR, .MagFilter = dg.FILTER_TYPE_LINEAR, .MipFilter = dg.FILTER_TYPE_LINEAR, .AddressU = dg.TEXTURE_ADDRESS_WRAP, .AddressV = dg.TEXTURE_ADDRESS_WRAP, .AddressW = dg.TEXTURE_ADDRESS_WRAP, .MipLODBias = 0, .MaxAnisotropy = 0, .ComparisonFunc = dg.COMPARISON_FUNC_NEVER, .BorderColor = [_]f32{ 0, 0, 0, 0 }, .MinLOD = 0, .MaxLOD = 3.402823466e+38, }; pub const Model = struct { model: dg.IGLTFModel_Wrapper, bindings: dg.GLTF_ModelResourceBindings, params: dg.GLTF_RenderInfo, node_indices: std.ArrayList(u32), node_transforms: std.ArrayList(Transform), const DEFAULT_PARAMS = dg.GLTF_RenderInfo{ .ModelTransform = @bitCast([16]f32, glm.Mat4.IDENTITY), .AlphaModes = dg.ALPHA_MODE_FLAG_ALL, .DebugView = 0, .OcclusionStrength = 1, .EmissionScale = 1, .IBLScale = 1, .AverageLogLum = 0.3, .MiddleGray = 0.18, .WhitePoint = 3, }; pub const Transform = extern struct { position: glm.Vec3, scale: glm.Vec3, rotation: glm.Vec4, matrix: glm.Mat4, }; pub fn initMemory(allocator: *Allocator, scene: *const Scene, data: []const u8) Model { const model = dg.Diligent_IGLTFModel_Create( @ptrCast(*dg.IRenderDevice, scene.dev.ptr), scene.ctx.ptr, &dg.IGLTFModelCreateInfo{ .FileName = null, .Data = data.ptr, .DataSize = data.len, .FileType = ._BINARY, .pTextureCache = null, .pCacheInfo = null, .LoadAnimationAndSkin = true, }, ); return .{ .model = dg.IGLTFModel_Wrapper.wrap(model), .bindings = scene.renderer.CreateResourceBindings( model, scene.camera_ubo.ptr, scene.light_ubo.ptr, ), .params = DEFAULT_PARAMS, .node_indices = std.ArrayList(u32).init(allocator), .node_transforms = std.ArrayList(Transform).init(allocator), }; } pub fn initFile(allocator: *Allocator, scene: *const Scene, path: [:0]const u8) Model { const model = dg.Diligent_IGLTFModel_Create( @ptrCast(*dg.IRenderDevice, scene.dev.ptr), scene.ctx.ptr, &dg.IGLTFModelCreateInfo{ .FileName = path.ptr, .Data = null, .DataSize = 0, .FileType = ._UNKNOWN, .pTextureCache = null, .pCacheInfo = null, .LoadAnimationAndSkin = true, }, ); return .{ .model = dg.IGLTFModel_Wrapper.wrap(model), .bindings = scene.renderer.CreateResourceBindings( model, scene.camera_ubo.ptr, scene.light_ubo.ptr, ), .params = DEFAULT_PARAMS, .node_indices = std.ArrayList(u32).init(allocator), .node_transforms = std.ArrayList(Transform).init(allocator), }; } pub fn deinit(self: *Model) void { self.bindings.Release(); self.model.Release(); self.node_transforms.deinit(); self.node_indices.deinit(); } pub fn setTransform(self: *Model, mat: glm.Mat4) void { self.params.ModelTransform = @bitCast([16]f32, mat); } pub fn draw(self: *Model, scene: *Scene) void { scene.renderer.Render( scene.ctx.ptr, self.model.ptr, &self.params, &self.bindings, null, scene.resource_binding.ptr, ); } pub fn loadNodeTransform(self: *Model, name: [:0]const u8) !void { var index: u32 = undefined; if (self.model.GetNodeIndex(name.ptr, &index)) { try self.node_indices.append(index); const transform = try self.node_transforms.addOne(); const ptr = @intToPtr(?*dg.GLTF_Transform, @ptrToInt(transform)); self.model.GetNodeTransform(index, ptr); } else { return error.NodeNotFound; } } pub fn flushTransforms(self: *Model) void { for (self.node_indices.items) |index, i| { const ptr = @intToPtr(?*dg.GLTF_Transform, @ptrToInt(&self.node_transforms.items[i])); self.model.SetNodeTransform(index, ptr); } self.model.UpdateTransforms(); } }; pub const Scene = struct { viewports: []dg.Viewport, scissors: []dg.Rect, resource_binding: dg.IShaderResourceBinding_Wrapper, camera_ubo: dg.IBuffer_Wrapper, light_ubo: dg.IBuffer_Wrapper, views_ubo: dg.IBuffer_Wrapper, views: []ViewUBO, renderer: dg.IGLTF_PBR_Renderer_Wrapper, models: std.ArrayList(Model), envmap: ?*dg.ITextureView, ctx: dg.IDeviceContext_Wrapper, dev: dg.IRenderDeviceVk_Wrapper, swc: dg.ISwapChainVk_Wrapper, const ViewUBO = packed struct { projection: glm.Mat4, modelview: glm.Mat4, }; pub fn init(gfx: *const graphics.Graphics) !Scene { const dev = gfx.dg_device; const swc = gfx.swapchain.dg_handle; var self: Scene = undefined; self.ctx = gfx.dg_ctx; self.dev = dev; self.swc = swc; self.viewports = try gfx.allocator.alloc(dg.Viewport, gfx.swapchain.image_rects.len); errdefer gfx.allocator.free(self.viewports); self.scissors = try gfx.allocator.alloc(dg.Rect, gfx.swapchain.image_rects.len); errdefer gfx.allocator.free(self.scissors); self.views = try gfx.allocator.alloc(ViewUBO, gfx.swapchain.image_rects.len); errdefer gfx.allocator.free(self.views); for (gfx.swapchain.image_rects) |rect, i| { self.viewports[i] = .{ .TopLeftX = @intToFloat(f32, rect.offset.x), .TopLeftY = @intToFloat(f32, rect.offset.y), .Width = @intToFloat(f32, rect.extent.width), .Height = @intToFloat(f32, rect.extent.height), .MinDepth = 0, .MaxDepth = 1, }; self.scissors[i] = .{ .left = rect.offset.x, .right = rect.offset.x + rect.extent.width, .top = rect.offset.y, .bottom = rect.offset.y + rect.extent.height, }; self.views[i] = .{ .projection = glm.Mat4.IDENTITY, .modelview = glm.Mat4.IDENTITY, }; } const geometry_shader = try self.createShader( "Geometry Shader", dg.SHADER_TYPE_GEOMETRY, dg.SHADER_SOURCE_LANGUAGE_GLSL, geometry_source, ); // VR Geometry shader ting { self.views_ubo = try self.createBuffer( [2]ViewUBO, "View Uniform Buffer", null, dg.BIND_UNIFORM_BUFFER, dg.USAGE_DYNAMIC, dg.CPU_ACCESS_WRITE, ); errdefer self.views_ubo.Release(); var resource_signature: ?*dg.IPipelineResourceSignature = null; const ubo_resource = dg.PipelineResourceDesc{ .Name = "Views", .ShaderStages = dg.SHADER_TYPE_GEOMETRY, .ArraySize = 1, .ResourceType = dg.SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, .VarType = dg.SHADER_RESOURCE_VARIABLE_TYPE_STATIC, .Flags = 0, }; self.dev.CreatePipelineResourceSignature(&dg.PipelineResourceSignatureDesc{ ._DeviceObjectAttribs = .{ .Name = "Multiview Geometry Instancing Signature" }, .Resources = &ubo_resource, .NumResources = 1, .ImmutableSamplers = null, .NumImmutableSamplers = 0, .BindingIndex = 1, .UseCombinedTextureSamplers = true, .CombinedSamplerSuffix = "_sampler", .SRBAllocationGranularity = 1, }, &resource_signature); const signature = dg.IPipelineResourceSignature_Wrapper.wrap(resource_signature); const shader_variable = signature.GetStaticVariableByName(dg.SHADER_TYPE_GEOMETRY, "Views"); dg.IShaderResourceVariable_Wrapper.wrap(shader_variable).Set(@ptrCast(*dg.IDeviceObject, self.views_ubo.ptr)); var resource_binding: ?*dg.IShaderResourceBinding = null; signature.CreateShaderResourceBinding(&resource_binding, true); self.resource_binding = dg.IShaderResourceBinding_Wrapper.wrap(resource_binding); errdefer self.resource_binding.Release(); } // GLTF const renderer = dg.Diligent_IGLTF_PBR_Renderer_Create( @ptrCast(*dg.IRenderDevice, self.dev.ptr), self.ctx.ptr, &dg.GLTF_RendererCreateInfo{ .RTVFmt = swc.GetDesc().*.ColorBufferFormat, .DSVFmt = swc.GetDesc().*.DepthBufferFormat, .FrontCCW = true, .AllowDebugView = false, .UseIBL = true, .UseAO = true, .UseEmissive = true, .UseImmutableSamplers = true, .UseTextureAtals = false, .ColorMapImmutableSampler = defaultSampler, .PhysDescMapImmutableSampler = defaultSampler, .NormalMapImmutableSampler = defaultSampler, .AOMapImmutableSampler = defaultSampler, .EmissiveMapImmutableSampler = defaultSampler, .MaxJointCount = 64, .pGS = geometry_shader, }, ); self.renderer = dg.IGLTF_PBR_Renderer_Wrapper.wrap(renderer); self.camera_ubo = try self.createBuffer( CameraAttribs, "Camera Uniform Buffer", null, dg.BIND_UNIFORM_BUFFER, dg.USAGE_DYNAMIC, dg.CPU_ACCESS_WRITE, ); errdefer self.camera_ubo.Release(); self.light_ubo = try self.createBuffer( LightAttribs, "Light Uniform Buffer", null, dg.BIND_UNIFORM_BUFFER, dg.USAGE_DYNAMIC, dg.CPU_ACCESS_WRITE, ); errdefer self.camera_ubo.Release(); const environment_map = try self.createTexture("Environment Map", "assets/papermill.ktx", dg.BIND_SHADER_RESOURCE, dg.USAGE_IMMUTABLE, 0); self.envmap = environment_map.GetDefaultView(dg.TEXTURE_VIEW_SHADER_RESOURCE); var barriers = [_]dg.StateTransitionDesc{ .{ .pResource = @ptrCast(*dg.IDeviceObject, self.camera_ubo.ptr), .FirstMipLevel = 0, .MipLevelsCount = dg.REMAINING_MIP_LEVELS, .FirstArraySlice = 0, .ArraySliceCount = dg.REMAINING_ARRAY_SLICES, .OldState = dg.RESOURCE_STATE_UNKNOWN, .NewState = dg.RESOURCE_STATE_CONSTANT_BUFFER, .TransitionType = dg.STATE_TRANSITION_TYPE_IMMEDIATE, .UpdateResourceState = true, }, .{ .pResource = @ptrCast(*dg.IDeviceObject, self.light_ubo.ptr), .FirstMipLevel = 0, .MipLevelsCount = dg.REMAINING_MIP_LEVELS, .FirstArraySlice = 0, .ArraySliceCount = dg.REMAINING_ARRAY_SLICES, .OldState = dg.RESOURCE_STATE_UNKNOWN, .NewState = dg.RESOURCE_STATE_CONSTANT_BUFFER, .TransitionType = dg.STATE_TRANSITION_TYPE_IMMEDIATE, .UpdateResourceState = true, }, .{ .pResource = @ptrCast(*dg.IDeviceObject, environment_map.ptr), .FirstMipLevel = 0, .MipLevelsCount = dg.REMAINING_MIP_LEVELS, .FirstArraySlice = 0, .ArraySliceCount = dg.REMAINING_ARRAY_SLICES, .OldState = dg.RESOURCE_STATE_UNKNOWN, .NewState = dg.RESOURCE_STATE_SHADER_RESOURCE, .TransitionType = dg.STATE_TRANSITION_TYPE_IMMEDIATE, .UpdateResourceState = true, }, }; self.ctx.TransitionResourceStates(barriers.len, &barriers); self.renderer.PrecomputeCubemaps(@ptrCast(*dg.IRenderDevice, self.dev.ptr), self.ctx.ptr, self.envmap); // model self.models = std.ArrayList(Model).init(gfx.allocator); return self; } pub fn deinit(self: *const Scene) void { self.views_ubo.Release(); self.light_ubo.Release(); self.camera_ubo.Release(); for (self.models.items) |*model| { model.deinit(); } self.models.deinit(); self.renderer.Release(); self.resource_binding.Release(); self.gfx.allocator.free(self.viewports); self.gfx.allocator.free(self.scissors); self.gfx.allocator.free(self.views); } pub fn render(self: *Scene, views: []const xr.View) !void { // update uniform buffers for (self.views) |*view, i| { const fov = views[i].fov; const viewport = self.viewports[i]; // const fovY = fov.angle_up - fov.angle_down; // const fovX = fov.angle_right - fov.angle_left; // const aspect = viewport.Width / viewport.Height; // view.projection = glm.perspective(fovX, aspect, 0.1, 100); view.projection = math.projection(fov, 0.1, 100); view.modelview = math.pose2matInverse(views[i].pose); } try self.updateBufferSlice(ViewUBO, self.views_ubo, self.views); try self.updateBuffer( CameraAttribs, self.camera_ubo, CameraAttribs.initFake(self.viewports[0], glm.Vec4.init([_]f32{ views[0].pose.position.x, views[0].pose.position.y, views[0].pose.position.z, 1, })), ); try self.updateBuffer(LightAttribs, self.light_ubo, .{ .direction = glm.Vec4.init([_]f32{ 1, 1, 1, 0 }), .intensity = glm.Vec4.init([_]f32{ 5, 3, 3, 3 }), }); // setup and clear render targets var rtv = self.swc.GetCurrentBackBufferRTV(); const dsv = self.swc.GetDepthBufferDSV(); self.ctx.SetRenderTargets(1, &rtv, dsv, dg.RESOURCE_STATE_TRANSITION_MODE_TRANSITION); self.ctx.SetViewports( @intCast(u32, self.viewports.len), self.viewports.ptr, 0, 0, ); self.ctx.SetScissorRects( @intCast(u32, self.scissors.len), self.scissors.ptr, 0, 0, ); const clear_color = [_]f32{ 0.35, 0., 0.35, 1 }; self.ctx.ClearRenderTarget(rtv, clear_color[0..], dg.RESOURCE_STATE_TRANSITION_MODE_TRANSITION); self.ctx.ClearDepthStencil( dsv, dg.CLEAR_DEPTH_FLAG, 1, 0, dg.RESOURCE_STATE_TRANSITION_MODE_TRANSITION, ); // record renderpass self.renderer.Begin(self.ctx.ptr); for (self.models.items) |*model| { model.draw(self); } // self.renderer.Render(self.ctx.ptr, self.model.ptr, &self.render_params, &self.model_bindings, null, self.resource_binding.ptr); } fn createShader( self: *const Scene, name: [:0]const u8, shader_type: dg.SHADER_TYPE, language: dg.SHADER_SOURCE_LANGUAGE, source: [:0]const u8, ) !*dg.IShader { var shader: ?*dg.IShader = null; self.dev.CreateShader(&dg.ShaderCreateInfo{ .Desc = .{ ._DeviceObjectAttribs = .{ .Name = name }, .ShaderType = shader_type, }, .Source = source, .SourceLanguage = language, // default .FilePath = null, .pShaderSourceStreamFactory = null, .ppConversionStream = null, .ByteCode = null, .ByteCodeSize = 0, .EntryPoint = "main", .Macros = null, .UseCombinedTextureSamplers = false, .CombinedSamplerSuffix = "_sampler", .ShaderCompiler = dg.SHADER_COMPILER_DEFAULT, .CompileFlags = 0, .HLSLVersion = .{ .Major = 0, .Minor = 0 }, .GLSLVersion = .{ .Major = 0, .Minor = 0 }, .GLESSLVersion = .{ .Major = 0, .Minor = 0 }, .ppCompilerOutput = null, }, &shader); return shader orelse error.ShaderCreationError; } fn createBuffer( self: *const Scene, comptime T: type, name: [:0]const u8, initial: ?*T, bind: dg.BIND_FLAGS, usage: dg.USAGE, cpu_access: dg.CPU_ACCESS_FLAGS, ) !dg.IBuffer_Wrapper { var buffer: ?*dg.IBuffer = null; var data_ptr: ?*const dg.BufferData = null; if (initial) |ptr| { const data = dg.BufferData{ .pData = ptr, .DataSize = @sizeOf(T), }; data_ptr = &data; } self.dev.CreateBuffer(&dg.BufferDesc{ ._DeviceObjectAttribs = .{ .Name = name }, .uiSizeInBytes = @sizeOf(T), .BindFlags = bind, // default: dg.BIND_NONE .Usage = usage, // default: dg.USAGE_DEFAULT .CPUAccessFlags = cpu_access, // default: dg.CPU_ACCESS_NONE .Mode = dg.BUFFER_MODE_UNDEFINED, .ElementByteStride = 0, .CommandQueueMask = 1, }, data_ptr, &buffer); return dg.IBuffer_Wrapper.wrap(buffer); } fn createTexture( self: *const Scene, name: [:0]const u8, path: [:0]const u8, bind: dg.BIND_FLAGS, usage: dg.USAGE, mip_levels: u32, ) !dg.ITexture_Wrapper { var texture: ?*dg.ITexture = null; dg.Diligent_CreateTextureFromFile(path, &dg.TextureLoadInfo{ .Name = name, .Usage = usage, // default: dg.USAGE_DEFAULT .BindFlags = bind, // default: dg.BIND_NONE .IsSRGB = true, .CPUAccessFlags = dg.CPU_ACCESS_NONE, .MipLevels = mip_levels, .GenerateMips = mip_levels > 0, .Format = dg.TEX_FORMAT_UNKNOWN, }, @ptrCast(*dg.IRenderDevice, self.dev.ptr), &texture); return dg.ITexture_Wrapper.wrap(texture); } fn updateBuffer( self: *const Scene, comptime T: type, buffer: dg.IBuffer_Wrapper, value: T, ) !void { var data: ?*T = null; self.ctx.MapBuffer(buffer.ptr, dg.MAP_WRITE, dg.MAP_FLAG_DISCARD, @ptrCast([*c]?*c_void, &data)); if (data) |ptr| { ptr.* = value; self.ctx.UnmapBuffer(buffer.ptr, dg.MAP_WRITE); } else { return error.MapFailed; } } fn updateBufferSlice( self: *const Scene, comptime T: type, buffer: dg.IBuffer_Wrapper, values: []const T, ) !void { var data: [*c]T = null; self.ctx.MapBuffer(buffer.ptr, dg.MAP_WRITE, dg.MAP_FLAG_DISCARD, @ptrCast([*c]?*c_void, &data)); if (data) |ptr| { mem.copy(T, data[0..values.len], values); self.ctx.UnmapBuffer(buffer.ptr, dg.MAP_WRITE); } else { return error.MapFailed; } } };