diff --git a/Cargo.toml b/Cargo.toml index cf25611..7bf05f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,5 +20,5 @@ pollster = "1.0.1" rand = "0.10.2" rayon = "1.12.0" tiff = "0.11.3" -wgpu = "30" +wgpu = {version = "30", features = ["spirv"]} winit = "0.30.13" diff --git a/shaders/Makefile b/shaders/Makefile new file mode 100644 index 0000000..888e342 --- /dev/null +++ b/shaders/Makefile @@ -0,0 +1,4 @@ +all: voxel.spv + +%.spv: %.slang + slangc $< -O3 -fvk-use-entrypoint-name -target spirv -o $@ diff --git a/shaders/example.slang b/shaders/example.slang deleted file mode 100644 index a191be2..0000000 --- a/shaders/example.slang +++ /dev/null @@ -1,2 +0,0 @@ -module example; - diff --git a/shaders/voxel.slang b/shaders/voxel.slang new file mode 100644 index 0000000..3ea6db7 --- /dev/null +++ b/shaders/voxel.slang @@ -0,0 +1,303 @@ +struct PushConstants +{ + float4x4 view_proj; + float3 cam_pos; + uint32_t frame_timestamp; +} + +public struct VertexOutput +{ + public float4 position : SV_Position; + + [vk::location(0)] + public float3 world_position; + + [vk::location(1)] + public nointerpolation uint32_t structure_id; + + [vk::location(2)] + public float3 cam_position; + + [vk::location(3)] + public float3 chunk_position; +} + +[[vk::push_constant]] +uniform PushConstants constants; + +[shader("vertex")] +VertexOutput chunk( + uint index: SV_VulkanVertexID, + [vk::location(0)] float3 chunk_position, + [vk::location(1)] uint id) +{ + let cube_vertices : float3[8] = + float3[]( + float3(0., 0., 0.), + float3(0., 0., 1.), + float3(1., 0., 1.), + float3(1., 0., 0.), + + float3(0., 1., 0.), + float3(0., 1., 1.), + float3(1., 1., 1.), + float3(1., 1., 0.), ); + // clang-format off + let cube_faces: int[24] = int[]( + // Bottom face + 1, 0, 2, 3, + + // Top face + 4, 5, 7, 6, + + // Side faces + 0, 1, 4, 5, + 1, 2, 5, 6, + 2, 3, 6, 7, + 3, 0, 7, 4, + ); + + let quad_index = index / (3 * 2); + let triangle_index = index % (3 * 2); + let triangle_map: int[6] = int[]( + 0, 1, 2, 1, 3, 2 + ); + + + let vertex = cube_vertices[cube_faces[quad_index * 4 + triangle_map[triangle_index]]]; + let output_vertex = mul(constants.view_proj, float4(vertex + chunk_position, 1.0f)); + + VertexOutput vertex_output; + vertex_output.position = output_vertex; + vertex_output.world_position = vertex + chunk_position; + vertex_output.structure_id = id; + vertex_output.cam_position = constants.cam_pos; + vertex_output.chunk_position = chunk_position; + + return vertex_output; +} + +struct StructurePointer +{ + uint32_t value; + + bool subdivided() + { + return (this.value & 0x80000000) != 0; + } + + bool pointer_valid() + { + return (this.value & 0x40000000) != 0; + } + + uint32_t pointer() + { + return this.value & 0x3FFFFFFF; + } +} + +struct ByteColor +{ + uint32_t byte_color; + + property uint32_t byte_r { + get {return byte_color & 0xFF;} + } + + property uint32_t byte_g { + get {return (byte_color >> 8) & 0xFF;} + } + + property uint32_t byte_b { + get {return (byte_color >> 16) & 0xFF;} + } + + property uint32_t byte_a { + get {return byte_color >> 24;} + } + + property float4 float_color { + get {return float4( + float(byte_r) / 255., + float(byte_g) / 255., + float(byte_b) / 255., + float(byte_a) / 255. + ); } + } +} + +struct StructurePoolElement +{ + StructurePointer pointers[64]; +} + +struct RequestBufferElement +{ + Atomic requests[64]; +} + +struct ColorPoolElement +{ + ByteColor colors[64]; +} + +struct LocationPoolElement +{ + uint32_t structure_id; + uint32_t structure_locator; +} + +[[vk::binding(0, 0)]] RWStructuredBuffer structure_pool; +[[vk::binding(1, 0)]] RWStructuredBuffer color_pool; +[[vk::binding(2, 0)]] RWStructuredBuffer location_pool; +[[vk::binding(3, 0)]] RWStructuredBuffer request_buffer; +[[vk::binding(4, 0)]] RWStructuredBuffer usage_buffer; +[[vk::binding(5, 0)]] RWStructuredBuffer structure_table_pointer; +[[vk::binding(6, 0)]] RWStructuredBuffer> structure_table_request_buffer; + +uint32_t3 get_children_pos(float3 position, uint32_t scale_exp) +{ + return (asuint(position) >> scale_exp) & 3; +} + +uint32_t get_children_index(float3 position, uint32_t scale_exp) +{ + // Get mantissa bits for this scale exp an retain bits for the specific children + uint32_t3 cell_position = (asuint(position) >> scale_exp) & 3; + return cell_position.x + cell_position.y * 4 + cell_position.z * 4 * 4; +} + +float3 floor_scale(float3 position, uint32_t scale_exp) +{ + uint32_t mask = ~0u << scale_exp; + return asfloat(asuint(position) & mask); +} + +float4 ray_march(float3 ray_direction, float3 ray_origin, uint32_t root_id, float dist_offset) +{ + let st_pointer = structure_table_pointer[root_id]; + if(!st_pointer.subdivided()) + { + discard; + } + + if(!st_pointer.pointer_valid()) + { + // Record request + structure_table_request_buffer[root_id].add(1); + discard; + } + + ray_origin += float3(1.); + float3 pos = ray_origin; + pos = clamp(pos, float(1.), asfloat(0x3fffffff)); + + uint32_t scale_exp = 23 - 2; + uint32_t node_stack[5] = + { + 0 + }; + uint32_t current_node_index = structure_table_pointer[root_id].pointer(); + + node_stack[10 - scale_exp / 2] = current_node_index; + + uint32_t child_index = get_children_index(pos, scale_exp); + StructurePointer current_node = structure_pool[current_node_index].pointers[child_index]; + + usage_buffer[current_node_index] = constants.frame_timestamp; + + + for(uint32_t iter = 0; iter < 500; iter ++) + { + //scale_exp = 23 - 2; + //current_node_index = structure_table_pointer[root_id].pointer(); + + child_index = get_children_index(pos, scale_exp); + current_node = structure_pool[current_node_index].pointers[child_index]; + + while(current_node.subdivided() && current_node.pointer_valid()) + { + scale_exp -= 2; + current_node_index = current_node.pointer(); + node_stack[10 - scale_exp / 2] = current_node_index; + child_index = get_children_index(pos, scale_exp); + current_node = structure_pool[current_node_index].pointers[child_index]; + // Write usage + usage_buffer[current_node_index] = constants.frame_timestamp; + } + + // Request subdiv + if(current_node.subdivided() && !current_node.pointer_valid()) + { + request_buffer[current_node_index].requests[child_index].add(1); + } + + if(color_pool[current_node_index].colors[child_index].byte_a != 0) + { + return color_pool[current_node_index].colors[child_index].float_color; + } + + // Perform dda + // Compute correct exponent, and shift it into the exponent part of floatt + let child_scale : float = asfloat((scale_exp - 23 + 127) << 23); + let child_pos : float3 = floor_scale(pos, scale_exp); + let child_far : float3 = child_pos + select(ray_direction > 0., float3(child_scale), float3(0.)); + // Intersection t + let inter_ts : float3 = (child_far - ray_origin) / ray_direction; + float inter_t = min(inter_ts.x, min(inter_ts.y, inter_ts.z)); + //return float4(inter_t); + + // Perform dda step + let neighbor_min : float3 = select(float3(inter_t) == inter_ts, child_pos + copysign(child_scale, ray_direction), child_pos); + let neighbor_max : float3 = asfloat(asint(neighbor_min) + ((1 << scale_exp) - 1)); + let previous_pos : float3 = pos; + pos = clamp(ray_origin + ray_direction * inter_t, neighbor_min, neighbor_max); + + /* + if(any(pos >= 2.) || any(pos < 1.)) + { + discard; + } + */ + + // Find most common ancestor + uint32_t3 diffs = asuint(child_pos) ^ asuint(pos); + uint32_t diff = (diffs.x | diffs.y | diffs.z); + + int32_t common_depth = (1 + (22 - firstbithigh(diff)) / 2) * 2; + if(common_depth <= 0) + { + discard; + } + + scale_exp = 23 - common_depth; + current_node_index = node_stack[10 - scale_exp / 2]; + } + + + return float4(1., 0., 1., 1.); +} + +[shader("fragment")] +float4 fragment(VertexOutput vertex_out) : SV_Target<0> +{ + let ray_direction = normalize(vertex_out.world_position - vertex_out.cam_position); + let intersection_t = box_intersect(vertex_out.cam_position, ray_direction, vertex_out.chunk_position, vertex_out.chunk_position + float3(1.)); + let local_ray_origin = max(intersection_t.x, 0.) * ray_direction + vertex_out.cam_position - vertex_out.chunk_position; + // Figure out intersection + return ray_march(ray_direction, local_ray_origin, vertex_out.structure_id, 0.); +} + +float2 box_intersect(float3 origin, float3 ray_direction, float3 box_min, float3 box_max) +{ + let min_ts = (box_min - origin) / ray_direction; + let max_ts = (box_max - origin) / ray_direction; + + let far_ts = max(min_ts, max_ts); + let near_ts = min(min_ts, max_ts); + + let far_t = min(far_ts.x, min(far_ts.y, far_ts.z)); + let near_t = max(near_ts.x, max(near_ts.y, near_ts.z)); + return float2(near_t, far_t); +} diff --git a/shaders/voxel.spv b/shaders/voxel.spv new file mode 100644 index 0000000..f51f0ce Binary files /dev/null and b/shaders/voxel.spv differ diff --git a/shaders/voxel.temp.wgsl b/shaders/voxel.temp.wgsl new file mode 100644 index 0000000..e7cf900 --- /dev/null +++ b/shaders/voxel.temp.wgsl @@ -0,0 +1,399 @@ +struct VertexOutput +{ + @builtin(position) postion: vec4, + @location(0) @interpolate(flat) chunk_index: u32, + @location(1) color: vec4, + @location(2) cam_pos: vec3, + @location(3) world_pos: vec3, + @location(4) @interpolate(flat) structure_id: u32, + @location(5) chunk_position: vec3 +} + +struct ChunkImmediate +{ + view_proj: mat4x4, + cam_pos: vec3, + frame_timestamp: u32, +} + +var constants: ChunkImmediate; +//var constants: ChunkInfo; + +struct CacheChunkObject +{ + transform: mat4x4, + color: vec4, + id: u32, + pointer: u32 +} + + +struct StructurePoolElement +{ + pointers: array +} + +struct RequestBufferElement +{ + requests: array, 64> +} + +struct ColorPoolElement +{ + colors: array +} + +struct LocationPoolElement +{ + structure_id: u32, + structure_locator: u32 +} + +struct SortedRequestsElement +{ + node: u32, + child: u32 +} + +fn unpack_color(color: u32) -> vec4 +{ + return vec4( + f32(color & 0xFF) / 255., + f32((color >> 8) & 0xFF) / 255., + f32((color >> 16) & 0xFF) / 255., + f32((color >> 24) & 0xFF) / 255. + ); +} + +@group(0) @binding(0) var structure_pool: array; +@group(0) @binding(1) var color_pool: array; +@group(0) @binding(2) var location_pool: array; +@group(0) @binding(3) var request_buffer: array; +@group(0) @binding(4) var usage_buffer: array>; +@group(0) @binding(5) var structure_table_pointer: array; +@group(0) @binding(6) var structure_table_request_buffer: array>; + +struct FragmentOutput { + @location(0) color: vec4, + @builtin(frag_depth) depth: f32, // Equivalent to gl_FragDepth +} + +@vertex +fn chunk(@builtin(vertex_index) index: u32, @location(0) position: vec3, @location(1) id: u32) -> @builtin(position) vec4 +{ + let cube_vertices = array, 8>( + vec3(0., 0., 0.), + vec3(0., 0., 1.), + vec3(1., 0., 1.), + vec3(1., 0., 0.), + + vec3(0., 1., 0.), + vec3(0., 1., 1.), + vec3(1., 1., 1.), + vec3(1., 1., 0.), + ); + + let cube_faces = array( + // Bottom face + 1, 0, 2, 3, + + // Top face + 4, 5, 7, 6, + + // Side faces + 0, 1, 4, 5, + 1, 2, 5, 6, + 2, 3, 6, 7, + 3, 0, 7, 4, + ); + + let quad_index = index / (3 * 2); + let triangle_index = index % (3 * 2); + let triangle_map = array( + 0, 1, 2, 1, 3, 2 + ); + + + let vertex = cube_vertices[cube_faces[quad_index * 4 + triangle_map[triangle_index]]]; + let output_vertex = constants.view_proj * vec4(vertex + position, 1.0f); + + return output_vertex; +} + + +struct StructureElement +{ + children: array +} + +struct ColorElement +{ + children: array, 64> +} + +struct LocationElement +{ + children: array, 64> +} + +struct RequestElement +{ + children: array, 64> +} + +fn box_inter(pos: vec3, ray_dir: vec3, box_min: vec3, box_max: vec3) -> vec2 +{ + let box_min_t = (box_min - pos) / ray_dir; + let box_max_t = (box_max - pos) / ray_dir; + + let near_ts = min(box_min_t, box_max_t); + let far_ts = max(box_min_t, box_max_t); + + let far_t = min(min(far_ts.x, far_ts.y), far_ts.z); + let near_t = max(max(near_ts.x, near_ts.y), near_ts.z); + + return vec2(near_t, far_t); +} + +fn sdf(voxel: vec3) -> bool +{ + let len = length(vec3(voxel) - vec3(128)) / 128.; + return len <= 1.; +} + +fn min_vec(x: vec3) -> f32 +{ + return min(x.x, min(x.y, x.z)); +} + +fn min_mask(x: vec3) -> vec3 +{ + let min = min(x.x, min(x.y, x.z)); + + return vec3(min == x.x, min == x.y, min == x.z); +} + +fn node_subdivided(node: u32) -> bool +{ + return ((node >> 31) & 1) != 0; +} + +fn node_pointer_valid(node: u32) -> bool +{ + return ((node >> 30) & 1) != 0; +} + +fn node_pointer(node: u32) -> u32 +{ + return node & 0x3FFFFFFF; +} + +fn voxel_from_wall(position: vec3, ray_dir: vec3) -> vec3 +{ + let integers = round(position); + let wall_mask = min_mask(abs(position - vec3(integers))); + let offsets = select(vec3(-0.5), vec3(0.5), ray_dir > vec3(0.)); + return vec3(floor(position + select(vec3(0.), offsets, wall_mask))); +} + +struct HitResult +{ + color: vec4, + hit_pos: vec3 +} + +fn new_traverse(ray_dir: vec3, ray_origin: vec3, root_id: u32, dist_offset: f32) -> HitResult +{ + let max_depth = 5; + let dist_offset_voxel = dist_offset * f32(1 << u32(max_depth * 2)); + let fovy_deg = 100. / 1920.; + let fovy_rad = (fovy_deg * 3.14) / 180.; + let cone_factor = tan(fovy_rad / 2.) * 2.; + + let st_pointer = structure_table_pointer[root_id]; + + + if (!node_subdivided(st_pointer)) + { + discard; + var result: HitResult; + result.color = vec4(0., 1., 0., 1.); + result.hit_pos = ray_origin; + return result; + } + if(!node_pointer_valid(st_pointer)) + { + // Node is subdivided, but not valid + // Send request on structure table + atomicAdd(&structure_table_request_buffer[root_id], 1); + + discard; + var result: HitResult; + result.color = vec4(0., 1., 0., 1.); + result.hit_pos = ray_origin; + return result; + } + //var current_node = node_pointer(st_pointer); + + var dfs_stack = array(node_pointer(st_pointer), 0, 0, 0, 0, 0); + var current_depth = 0; + var current_node = dfs_stack[current_depth]; + + usage_buffer[current_node] = constants.frame_timestamp; + + // Start location + //let voxel_dir = select(vec3(-1), vec3(1), ray_dir >= vec3(0.)); + var node_shift = (max_depth - current_depth) * 2; + + var child_size = 1 << u32(node_shift - 2); + var node_size = 1 << u32(node_shift); + + var pos_origin = clamp(ray_origin * f32(1 << u32(max_depth * 2)), vec3(0.), vec3(f32(node_size) - 1.)); + var voxel = vec3(pos_origin); + var far_t = 0.; + var inv_ray_dir = 1. / ray_dir; + var ray_positive = ray_dir > vec3(0.); + var step_dir = select(vec3(-1), vec3(1), ray_positive); + + for(var iter = 0; iter < 400; iter ++) + { + // Shift into voxel position + node_shift = (max_depth - current_depth) * 2; + child_size = 1 << u32(node_shift - 2); + + // Compute child position position from voxel position + var child_pos = (voxel >> vec3(u32(node_shift - 2))) & vec3(3); + // Compute child index in pointers + var child_index = child_pos.x + child_pos.y * 4 + child_pos.z * 4 * 4; + // Candidate child pointer + var pointer = structure_pool[current_node].pointers[child_index]; + + // Descent loop + let min_child_size = (length(vec3(voxel) - pos_origin) + dist_offset_voxel) * cone_factor; + while(node_subdivided(pointer) && node_pointer_valid(pointer) && + f32(child_size / 4) >= min_child_size + ) + { + + // Descend + current_depth += 1; + + // Try to descend again + node_shift = (max_depth - current_depth) * 2; + child_size = 1 << u32(node_shift - 2); + child_pos = (voxel >> vec3(u32(node_shift - 2))) & vec3(3); + current_node = node_pointer(pointer); + dfs_stack[current_depth] = current_node; + child_index = child_pos.x + child_pos.y * 4 + child_pos.z * 4 * 4; + + pointer = structure_pool[current_node].pointers[child_index]; + + // Record usage in usage buffer + usage_buffer[current_node] = constants.frame_timestamp; + } + + // If we could not descencd, request the child + if(node_subdivided(pointer) && !node_pointer_valid(pointer) && + f32(child_size / 4) >= min_child_size) + { + // Record request + atomicAdd(&request_buffer[dfs_stack[current_depth]].requests[child_index], 1); + } + + // Check color + let color = color_pool[current_node].colors[child_index]; + if(((color >> 24) & 0xFF) != 0) + { + var result: HitResult; + result.color = unpack_color(color); + result.hit_pos = (far_t / f32(1 << u32(max_depth * 2))) * ray_dir + ray_origin; + return result; + } + + // Advance + child_pos = voxel & vec3(i32(0xFFFFFFFF << u32(node_shift - 2))); + let far_wall = child_pos + select(vec3(0), vec3(child_size), ray_positive); + let far_wall_inter = (vec3(far_wall) - pos_origin) * inv_ray_dir; + far_t = min(min(far_wall_inter.x, far_wall_inter.y), far_wall_inter.z); + + // Perform dda step on the children scale + let next_child = select(child_pos, child_pos + step_dir * vec3(child_size), vec3(far_t) == far_wall_inter); + + let previous_voxel = voxel; + voxel = clamp(vec3(pos_origin + far_t * ray_dir), next_child, next_child + vec3(child_size) - vec3(1)); + + if any(voxel < vec3(0)) || any(voxel >= vec3(1 << u32((max_depth * 2)))) + { + discard; + } + + // We touched a voxel as if we explored blocks sized by the child size of the current node. + // But we might have exited the current node. + + // If this is the case we have to walk back up the tree + // And then back down to the next node over + + // As such we find the lowest ancestor that can contain both the privous voxel (in node) and the new voxel (out of node) + let bit_diffs = voxel ^ previous_voxel; + let bit_diffs_lowest = bit_diffs.x | bit_diffs.y | bit_diffs.z; + + let common_depth = ((countLeadingZeros(bit_diffs_lowest) - i32(32 - max_depth * 2)) / 2); + + current_depth = common_depth; + current_node = dfs_stack[current_depth]; + } + + // Iter max color + var result: HitResult; + result.color = vec4(1., 0., 1., 1.); + result.hit_pos = (far_t / f32(1 << u32(max_depth * 2))) * ray_dir + ray_origin; + return result; +} + +@fragment +fn fragment() -> @location(0) vec4 +{ + return vec4(1., 0., 0., 1.) ; +} + +@early_depth_test(less_equal) +@fragment +fn _fragment(in: VertexOutput) -> FragmentOutput +{ + //frag_out.color = vec4(2 * 0.01 / (100. + 0.01 - depth * (100. - 0.01))); + let ray_dir = normalize(in.world_pos - in.cam_pos); + let interp = box_inter(in.cam_pos - in.chunk_position, ray_dir, vec3(0.), vec3(1)); + let ray_origin = (in.cam_pos - in.chunk_position) + ray_dir * (max(0., interp.x)); + + + let result = new_traverse(ray_dir, ray_origin, in.structure_id, length(in.cam_pos - (ray_origin + in.chunk_position))); + let clip_pos = constants.view_proj * vec4(result.hit_pos + in.chunk_position, 1.); + let depth = clip_pos.z / clip_pos.w; + var frag_out: FragmentOutput; + //frag_out.color = result.color; + frag_out.color = result.color; + frag_out.depth = depth; + return frag_out; + + //return vec4(ray_origin, 1.); + //return frag_out; + //return vec4(interp.y / 10.); +} + +/* +@fragment +fn fragment(in: VertexOutput) -> @location(0) vec4 +{ + let st = structure_table_pointer[0]; + let subdivided = ((st >> 31) & 1) != 0; + let pointer_valid = ((st >> 30) & 1) != 0; + // Request stuff + atomicAdd(&structure_table_request_buffer[0], 1); + if(subdivided && !pointer_valid) + { + return vec4(0., 1., 0., 1.); + } + return vec4(1., 0., 0., 1.); +} +*/ + diff --git a/src/main.rs b/src/main.rs index 8479253..30554b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,6 +32,8 @@ use wgpu::RenderPipeline; use wgpu::Texture; use wgpu::TextureUsages; use wgpu::TextureView; +use wgpu::include_spirv; +use wgpu::include_wgsl; use wgpu::util::BufferInitDescriptor; use wgpu::util::DeviceExt; use wgpu::util::DownloadBuffer; @@ -44,6 +46,7 @@ use winit::event_loop::ActiveEventLoop; use winit::event_loop::ControlFlow; use winit::event_loop::EventLoop; use winit::event_loop::OwnedDisplayHandle; +use winit::platform::x11::EventLoopBuilderExtX11; use winit::window::Window; use winit::window::WindowId; @@ -200,14 +203,29 @@ impl State usage: BufferUsages::COPY_DST | BufferUsages::VERTEX, }); - let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Main shader module"), - source: wgpu::ShaderSource::Wgsl( - std::fs::read_to_string("shaders/voxel.wgsl") - .unwrap() - .into(), - ), - }); + // let shader_module = unsafe { + // device.create_shader_module_trusted( + // wgpu::ShaderModuleDescriptor { + // label: Some("Main shader module"), + // source: wgpu::ShaderSource::Wgsl( + // std::fs::read_to_string("shaders/voxel.wgsl") + // .unwrap() + // .into(), + // ), + // }, + // wgpu::ShaderRuntimeChecks { + // bounds_checks: false, + // force_loop_bounding: false, + // ray_query_initialization_tracking: false, + // task_shader_dispatch_tracking: false, + // mesh_shader_primitive_indices_clamp: false, + // int_div_checks: false, + // }, + // ) + // }; + + //let shader_module = device.create_shader_module(include_wgsl!("../shaders/voxel.wgsl")); + let shader_module = device.create_shader_module(include_spirv!("../shaders/voxel.spv")); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Voxel pipeline layout"),