├── .gitignore ├── COPYING ├── README.md ├── data ├── LICENSE.txt ├── cerberus-albedo.png ├── cerberus-normal.png ├── cerberus-roughness-metallic.png ├── cerberus.obj └── shaders │ ├── .gitignore │ ├── build.bat │ ├── build.sh │ ├── mipmap.frag.glsl │ ├── mipmap.frag.spv │ ├── mipmap.vert.glsl │ ├── mipmap.vert.spv │ ├── mipmap.wgsl │ ├── pbr.frag.glsl │ ├── pbr.frag.spv │ ├── pbr.vert.glsl │ ├── pbr.vert.spv │ └── pbr.wgsl ├── dub.json ├── gamecontrollerdb.txt ├── input.conf ├── lib ├── x64 │ └── SDL2.dll └── x86 │ └── SDL2.dll ├── screenshot.jpg └── src ├── dgpu ├── asset │ ├── camera.d │ ├── entity.d │ ├── geometry.d │ ├── image.d │ ├── io │ │ └── obj.d │ ├── material.d │ ├── mipmap.d │ ├── scene.d │ ├── texture.d │ └── trimesh.d ├── core │ ├── application.d │ ├── config.d │ ├── event.d │ ├── gpu.d │ ├── input.d │ ├── keycodes.d │ ├── locale.d │ ├── logger.d │ ├── package.d │ ├── props.d │ ├── resource.d │ ├── time.d │ └── vfs.d └── render │ ├── drawable.d │ ├── pass.d │ ├── pipeline.d │ ├── renderer.d │ ├── resource.d │ ├── shader.d │ ├── target.d │ └── vertexbuffer.d ├── freeview.d └── main.d /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.o 3 | *.obj 4 | 5 | # Compiled Static libraries 6 | /*.a 7 | /*.lib 8 | 9 | # Executables 10 | /*.exe 11 | 12 | # Libraries 13 | /*.dll 14 | /*.so 15 | /*.dylib 16 | 17 | # Dub files 18 | .dub 19 | dub.selections.json 20 | 21 | # Dub test files 22 | __test__library__ 23 | tests 24 | 25 | # Documentation 26 | docs/ 27 | docs.json 28 | 29 | # Coverage files 30 | *.lst 31 | 32 | # Debug info 33 | *.pdb 34 | 35 | # Backup files 36 | *~ 37 | 38 | # Log files 39 | log.txt 40 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGPU demo in D 2 | A minimal WebGPU-based render engine that can load and display textured and shaded OBJ files. Uses [bindbc-wgpu](https://github.com/gecko0307/bindbc-wgpu) to link WebGPU functions and SDL2 to create a window. Written in [D language](https://dlang.org). 3 | 4 | The demo renders a Cerberus model by Andrew Maximov. 5 | 6 | [![Screenshot](screenshot.jpg)](screenshot.jpg) 7 | 8 | **Warning: highly experimental!** I've tested it on Windows and Linux so far. It probably should work on macOS, however has not been tested yet. 9 | 10 | To run the application, you should install [SDL2](https://www.libsdl.org) and [wgpu-native 22.1](https://github.com/gfx-rs/wgpu-native). Under Windows, `SDL2.dll` and `wgpu_native.dll` can be copied to the application directory. 11 | 12 | ### What is WebGPU? 13 | It is a new low-level graphics and compute API for the Web that works on top of Vulkan, DirectX 12, or Metal. It exposes the generic computational facilities available in today's GPUs in a cross-platform way. 14 | 15 | [wgpu-native](https://github.com/gfx-rs/wgpu-native) is a native WebGPU implementation in Rust that compiles to a library with C API, which can be used in any language. This potentially makes WebGPU a viable and future-proof alternative to OpenGL for cross-platform graphics development. 16 | 17 | ### Is this going to be a full-blown game engine? 18 | Probably. Let's wait for the API to stabilize first. 19 | -------------------------------------------------------------------------------- /data/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Cerberus model by Andrew Maximov. Licensed under the Creative Commons Attribution 4.0. -------------------------------------------------------------------------------- /data/cerberus-albedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gecko0307/wgpu-dlang/3bead903885c3364c2380cea77b720de21542be2/data/cerberus-albedo.png -------------------------------------------------------------------------------- /data/cerberus-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gecko0307/wgpu-dlang/3bead903885c3364c2380cea77b720de21542be2/data/cerberus-normal.png -------------------------------------------------------------------------------- /data/cerberus-roughness-metallic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gecko0307/wgpu-dlang/3bead903885c3364c2380cea77b720de21542be2/data/cerberus-roughness-metallic.png -------------------------------------------------------------------------------- /data/shaders/.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | -------------------------------------------------------------------------------- /data/shaders/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | glslangValidator mipmap.vert.glsl -V -o mipmap.vert.spv 3 | glslangValidator mipmap.frag.glsl -V -o mipmap.frag.spv 4 | glslangValidator pbr.vert.glsl -V -o pbr.vert.spv 5 | glslangValidator pbr.frag.glsl -V -o pbr.frag.spv -------------------------------------------------------------------------------- /data/shaders/build.sh: -------------------------------------------------------------------------------- 1 | glslangValidator mipmap.vert.glsl -V -o mipmap.vert.spv 2 | glslangValidator mipmap.frag.glsl -V -o mipmap.frag.spv 3 | glslangValidator pbr.vert.glsl -V -o pbr.vert.spv 4 | glslangValidator pbr.frag.glsl -V -o pbr.frag.spv 5 | -------------------------------------------------------------------------------- /data/shaders/mipmap.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(set = 0, binding = 0) uniform sampler texSampler; 4 | layout(set = 0, binding = 1) uniform texture2D tex; 5 | 6 | layout(location = 0) in vec2 texCoord; 7 | 8 | layout(location = 0) out vec4 fragColor; 9 | 10 | void main() 11 | { 12 | fragColor = texture(sampler2D(tex, texSampler), texCoord); 13 | } 14 | -------------------------------------------------------------------------------- /data/shaders/mipmap.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gecko0307/wgpu-dlang/3bead903885c3364c2380cea77b720de21542be2/data/shaders/mipmap.frag.spv -------------------------------------------------------------------------------- /data/shaders/mipmap.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | vec2 positions[3] = vec2[3]( 4 | vec2(-1.0, -1.0), 5 | vec2(-1.0, 3.0), 6 | vec2(3.0, -1.0) 7 | ); 8 | 9 | layout(location = 0) out vec2 texCoord; 10 | 11 | out gl_PerVertex 12 | { 13 | vec4 gl_Position; 14 | }; 15 | 16 | void main() 17 | { 18 | texCoord = positions[gl_VertexIndex] * vec2(0.5, -0.5) + vec2(0.5); 19 | gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); 20 | } 21 | -------------------------------------------------------------------------------- /data/shaders/mipmap.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gecko0307/wgpu-dlang/3bead903885c3364c2380cea77b720de21542be2/data/shaders/mipmap.vert.spv -------------------------------------------------------------------------------- /data/shaders/mipmap.wgsl: -------------------------------------------------------------------------------- 1 | var positions: array, 3> = array, 3>(vec2(-1.0, -1.0), vec2(-1.0, 3.0), vec2(3.0, -1.0)); 2 | 3 | struct VertexOutput 4 | { 5 | @builtin(position) position: vec4, 6 | @location(0) texCoord: vec2, 7 | } 8 | 9 | @vertex 10 | fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput 11 | { 12 | var output: VertexOutput; 13 | output.texCoord = positions[vertexIndex] * vec2(0.5, -0.5) + vec2(0.5); 14 | output.position = vec4(positions[vertexIndex], 0.0, 1.0); 15 | return output; 16 | } 17 | 18 | @group(0) @binding(0) var texSampler: sampler; 19 | @group(0) @binding(1) var tex: texture_2d; 20 | 21 | @fragment 22 | fn fs_main(@location(0) texCoord: vec2) -> @location(0) vec4 23 | { 24 | return textureSample(tex, texSampler, texCoord); 25 | } 26 | -------------------------------------------------------------------------------- /data/shaders/pbr.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #define PI 3.14159265359 4 | const float PI2 = PI * 2.0; 5 | const float invPI = 1.0 / PI; 6 | 7 | // TODO: layout(set = 1, binding = 0) uniform UniformsPass; 8 | 9 | layout(set = 2, binding = 0) uniform UniformsMaterial 10 | { 11 | vec4 baseColorFactor; 12 | vec4 pbrRoughnessMetallicFactor; 13 | } material; 14 | 15 | layout(set = 2, binding = 1) uniform sampler baseColorSampler; 16 | layout(set = 2, binding = 2) uniform texture2DArray baseColorTexture; 17 | 18 | layout(set = 2, binding = 3) uniform sampler normalSampler; 19 | layout(set = 2, binding = 4) uniform texture2DArray normalTexture; 20 | 21 | layout(set = 2, binding = 5) uniform sampler roughnessMetallicSampler; 22 | layout(set = 2, binding = 6) uniform texture2DArray roughnessMetallicTexture; 23 | 24 | vec3 toLinear(vec3 v) 25 | { 26 | return pow(v, vec3(2.2)); 27 | } 28 | 29 | vec3 toGamma(vec3 v) 30 | { 31 | return pow(v, vec3(1.0 / 2.2)); 32 | } 33 | 34 | mat3 cotangentFrame(in vec3 N, in vec3 p, in vec2 uv) 35 | { 36 | vec3 pos_dx = dFdx(p); 37 | vec3 pos_dy = dFdy(p); 38 | vec2 st1 = dFdx(uv); 39 | vec2 st2 = dFdy(uv); 40 | vec3 T = (st2.y * pos_dx - st1.y * pos_dy) / (st1.x * st2.y - st2.x * st1.y); 41 | T = normalize(T - N * dot(N, T)); 42 | vec3 B = normalize(cross(N, T)); 43 | return mat3(T, B, N); 44 | } 45 | 46 | float distributionGGX(vec3 N, vec3 H, float roughness) 47 | { 48 | float a = roughness * roughness; 49 | float a2 = a * a; 50 | float NdotH = max(dot(N, H), 0.0); 51 | float NdotH2 = NdotH * NdotH; 52 | float num = a2; 53 | float denom = max(NdotH2 * (a2 - 1.0) + 1.0, 0.001); 54 | denom = PI * denom * denom; 55 | return num / denom; 56 | } 57 | 58 | float geometrySchlickGGX(float NdotV, float roughness) 59 | { 60 | float r = (roughness + 1.0); 61 | float k = (r * r) / 8.0; 62 | float num = NdotV; 63 | float denom = NdotV * (1.0 - k) + k; 64 | return num / denom; 65 | } 66 | 67 | float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) 68 | { 69 | float NdotV = max(dot(N, V), 0.0); 70 | float NdotL = max(dot(N, L), 0.0); 71 | float ggx2 = geometrySchlickGGX(NdotV, roughness); 72 | float ggx1 = geometrySchlickGGX(NdotL, roughness); 73 | return ggx1 * ggx2; 74 | } 75 | 76 | vec3 fresnelRoughness(float cosTheta, vec3 f0, float roughness) 77 | { 78 | return f0 + (max(vec3(1.0 - roughness), f0) - f0) * pow(1.0 - cosTheta, 5.0); 79 | } 80 | 81 | layout(location = 0) in vec4 position; 82 | layout(location = 1) in vec2 texCoord; 83 | layout(location = 2) in vec4 normal; 84 | 85 | layout(location = 0) out vec4 outColor; 86 | 87 | void main() 88 | { 89 | vec3 N = normalize(normal.xyz); 90 | const vec3 E = normalize(-position.xyz); 91 | const mat3 tangentToEye = cotangentFrame(N, position.xyz, texCoord); 92 | 93 | vec3 tangentNormal = texture(sampler2DArray(normalTexture, normalSampler), vec3(texCoord, 0)).xyz; 94 | tangentNormal = normalize(tangentNormal * 2.0 - 1.0); 95 | tangentNormal.y = -tangentNormal.y; 96 | N = normalize(tangentToEye * tangentNormal); 97 | 98 | const vec3 L = normalize(vec3(1.0, 1.0, 1.0)); 99 | const float lightEnergy = 5.0; 100 | 101 | const vec3 albedo = toLinear(texture(sampler2DArray(baseColorTexture, baseColorSampler), vec3(texCoord, 0)).rgb); 102 | const float roughness = texture(sampler2DArray(roughnessMetallicTexture, roughnessMetallicSampler), vec3(texCoord, 0)).g; 103 | const float metallic = texture(sampler2DArray(roughnessMetallicTexture, roughnessMetallicSampler), vec3(texCoord, 0)).b; 104 | 105 | const vec3 f0 = mix(vec3(0.04), albedo, metallic); 106 | const float NL = max(dot(N, L), 0.0); 107 | const vec3 H = normalize(E + L); 108 | float NDF = distributionGGX(N, H, roughness); 109 | float G = geometrySmith(N, E, L, roughness); 110 | const vec3 F = fresnelRoughness(max(dot(H, E), 0.0), f0, roughness); 111 | const vec3 kD = (1.0 - F) * (1.0 - metallic); 112 | const vec3 specular = (NDF * G * F) / max(4.0 * max(dot(N, E), 0.0) * NL, 0.001); 113 | const vec3 ambient = toLinear(vec3(0.1)); 114 | const vec3 radiance = albedo * ambient + (kD * albedo * invPI + specular) * NL * lightEnergy; 115 | 116 | outColor = vec4(toGamma(radiance), 1.0); 117 | } 118 | -------------------------------------------------------------------------------- /data/shaders/pbr.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gecko0307/wgpu-dlang/3bead903885c3364c2380cea77b720de21542be2/data/shaders/pbr.frag.spv -------------------------------------------------------------------------------- /data/shaders/pbr.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(set = 0, binding = 0) uniform UniformsRenderer 4 | { 5 | mat4 viewMatrix; 6 | mat4 invViewMatrix; 7 | mat4 projectionMatrix; 8 | vec4 view; 9 | } renderer; 10 | 11 | layout(set = 3, binding = 0) uniform UniformsEntity { 12 | mat4 modelViewMatrix; 13 | mat4 normalMatrix; 14 | } entity; 15 | 16 | layout(location = 0) in vec3 vaVertex; 17 | layout(location = 1) in vec2 vaTexcoord; 18 | layout(location = 2) in vec3 vaNormal; 19 | 20 | layout(location = 0) out vec4 outPositionEye; 21 | layout(location = 1) out vec2 outTexcoord; 22 | layout(location = 2) out vec4 outNormalEye; 23 | 24 | out gl_PerVertex 25 | { 26 | vec4 gl_Position; 27 | }; 28 | 29 | void main() 30 | { 31 | vec4 eyeVertex = entity.modelViewMatrix * vec4(vaVertex, 1.0); 32 | outPositionEye = eyeVertex; 33 | outNormalEye = (entity.normalMatrix * vec4(vaNormal, 0.0)); 34 | outTexcoord = vaTexcoord; 35 | gl_Position = renderer.projectionMatrix * eyeVertex; 36 | } 37 | -------------------------------------------------------------------------------- /data/shaders/pbr.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gecko0307/wgpu-dlang/3bead903885c3364c2380cea77b720de21542be2/data/shaders/pbr.vert.spv -------------------------------------------------------------------------------- /data/shaders/pbr.wgsl: -------------------------------------------------------------------------------- 1 | struct UniformsRenderer { 2 | viewMatrix: mat4x4, 3 | invViewMatrix: mat4x4, 4 | projectionMatrix: mat4x4, 5 | view: vec4 6 | }; 7 | 8 | struct UniformsMaterial { 9 | baseColorFactor: vec4, 10 | pbrRoughnessMetallicFactor: vec4 11 | }; 12 | 13 | struct UniformsEntity { 14 | modelViewMatrix: mat4x4, 15 | normalMatrix: mat4x4 16 | }; 17 | 18 | @group(0) @binding(0) var renderer: UniformsRenderer; 19 | 20 | // TODO: @group(1) @binding(0) var pass: UniformsPass; 21 | 22 | @group(2) @binding(0) var material: UniformsMaterial; 23 | @group(2) @binding(1) var baseColorSampler: sampler; 24 | @group(2) @binding(2) var baseColorTexture: texture_2d_array; 25 | @group(2) @binding(3) var normalSampler: sampler; 26 | @group(2) @binding(4) var normalTexture: texture_2d_array; 27 | @group(2) @binding(5) var roughnessMetallicSampler: sampler; 28 | @group(2) @binding(6) var roughnessMetallicTexture: texture_2d_array; 29 | 30 | @group(3) @binding(0) var entity: UniformsEntity; 31 | 32 | struct VertexInput 33 | { 34 | @location(0) position: vec3, 35 | @location(1) texcoord: vec2, 36 | @location(2) normal: vec3 37 | }; 38 | 39 | struct VertexOutput 40 | { 41 | @builtin(position) position: vec4, 42 | @location(0) positionEye: vec4, 43 | @location(1) texcoord: vec2, 44 | @location(2) normal: vec4 45 | }; 46 | 47 | @vertex 48 | fn vs_main(input: VertexInput) -> VertexOutput 49 | { 50 | var output: VertexOutput; 51 | let positionEye = entity.modelViewMatrix * vec4(input.position, 1.0); 52 | output.position = renderer.projectionMatrix * positionEye; 53 | output.positionEye = positionEye; 54 | output.texcoord = input.texcoord; 55 | output.normal = entity.normalMatrix * vec4(input.normal, 0.0); 56 | return output; 57 | } 58 | 59 | fn toLinear(v: vec3) -> vec3 60 | { 61 | return pow(v, vec3(2.2)); 62 | } 63 | 64 | fn toGamma(v: vec3) -> vec3 65 | { 66 | return pow(v, vec3(1.0 / 2.2)); 67 | } 68 | 69 | fn cotangentFrame(N: vec3, p: vec3, uv: vec2) -> mat3x3 70 | { 71 | let pos_dx = dpdx(p); 72 | let pos_dy = dpdy(p); 73 | let st1 = dpdx(uv); 74 | let st2 = dpdy(uv); 75 | var T = (st2.y * pos_dx - st1.y * pos_dy) / (st1.x * st2.y - st2.x * st1.y); 76 | T = normalize(T - N * dot(N, T)); 77 | let B = normalize(cross(N, T)); 78 | return mat3x3(T, B, N); 79 | } 80 | 81 | fn distributionGGX(N: vec3, H: vec3, roughness: f32) -> f32 82 | { 83 | let PI: f32 = 3.14159265359; 84 | let a = roughness * roughness; 85 | let a2 = a * a; 86 | let NdotH = max(dot(N, H), 0.0); 87 | let NdotH2 = NdotH * NdotH; 88 | let num = a2; 89 | var denom = max(NdotH2 * (a2 - 1.0) + 1.0, 0.001); 90 | denom = PI * denom * denom; 91 | return num / denom; 92 | } 93 | 94 | fn geometrySchlickGGX(NdotV: f32, roughness: f32) -> f32 95 | { 96 | let r = (roughness + 1.0); 97 | let k = (r*r) / 8.0; 98 | let num = NdotV; 99 | let denom = NdotV * (1.0 - k) + k; 100 | return num / denom; 101 | } 102 | 103 | fn geometrySmith(N: vec3, V: vec3, L: vec3, roughness: f32) -> f32 104 | { 105 | let NdotV = max(dot(N, V), 0.0); 106 | let NdotL = max(dot(N, L), 0.0); 107 | let ggx2 = geometrySchlickGGX(NdotV, roughness); 108 | let ggx1 = geometrySchlickGGX(NdotL, roughness); 109 | return ggx1 * ggx2; 110 | } 111 | 112 | fn fresnelRoughness(cosTheta: f32, f0: vec3, roughness: f32) -> vec3 113 | { 114 | return f0 + (max(vec3(1.0 - roughness), f0) - f0) * pow(1.0 - cosTheta, 5.0); 115 | } 116 | 117 | struct FragmentInput 118 | { 119 | @location(0) position: vec4, 120 | @location(1) texcoord: vec2, 121 | @location(2) normal: vec4 122 | }; 123 | 124 | @fragment 125 | fn fs_main(input: FragmentInput) -> @location(0) vec4 126 | { 127 | let PI: f32 = 3.14159265359; 128 | let invPI: f32 = 1.0 / PI; 129 | 130 | var N = normalize(input.normal.xyz); 131 | let E = normalize(-input.position.xyz); 132 | let tangentToEye = cotangentFrame(N, input.position.xyz, input.texcoord); 133 | 134 | var tangentNormal = textureSample(normalTexture, normalSampler, input.texcoord, 0).rgb; 135 | tangentNormal = tangentNormal * 2.0 - 1.0; 136 | tangentNormal.y = -tangentNormal.y; 137 | N = normalize(tangentToEye * tangentNormal); 138 | 139 | let L = normalize(vec3(0.0, 0.0, 1.0)); 140 | let lightEnergy = 5.0; 141 | 142 | let albedo = toLinear(textureSample(baseColorTexture, baseColorSampler, input.texcoord, 0).rgb); 143 | let roughness = textureSample(roughnessMetallicTexture, roughnessMetallicSampler, input.texcoord, 0).g; 144 | let metallic = textureSample(roughnessMetallicTexture, roughnessMetallicSampler, input.texcoord, 0).b; 145 | 146 | let f0 = mix(vec3(0.04), albedo, metallic); 147 | let NL = max(dot(N, L), 0.0); 148 | let H = normalize(E + L); 149 | let NDF = distributionGGX(N, H, roughness); 150 | let G = geometrySmith(N, E, L, roughness); 151 | let F = fresnelRoughness(max(dot(H, E), 0.0), f0, roughness); 152 | let kD = (1.0 - F) * (1.0 - metallic); 153 | let specular = (NDF * G * F) / max(4.0 * max(dot(N, E), 0.0) * NL, 0.001); 154 | let ambient = toLinear(vec3(0.1, 0.1, 0.1)); 155 | let radiance = albedo * ambient + (kD * albedo * invPI + specular) * NL * lightEnergy; 156 | 157 | return vec4(toGamma(radiance), 1.0); 158 | } 159 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "description": "Dagon over WebGPU", 4 | "authors": [ 5 | "Timur Gafarov" 6 | ], 7 | "copyright": "Copyright © 2021, Timur Gafarov", 8 | "license": "BSL-1.0", 9 | 10 | "dependencies": { 11 | "dlib": "~>1.3.0", 12 | "bindbc-wgpu": "0.22.0", 13 | "bindbc-sdl": "~>1.0.1" 14 | }, 15 | 16 | "importPaths": [ 17 | "src" 18 | ], 19 | 20 | "sourcePaths": [ 21 | "src" 22 | ], 23 | 24 | "versions": [ 25 | "SDL_2014" 26 | ], 27 | 28 | "buildRequirements":[ 29 | "allowWarnings" 30 | ], 31 | 32 | "libs-windows-x86": [ 33 | ], 34 | "libs-windows-x86_64": [ 35 | "user32" 36 | ], 37 | 38 | "lflags-linux": ["-rpath=."], 39 | "lflags-linux-x86_64": [ 40 | ], 41 | 42 | "copyFiles-windows-x86": ["lib/x86/*.dll"], 43 | "copyFiles-windows-x86_64": ["lib/x64/*.dll"] 44 | } 45 | -------------------------------------------------------------------------------- /input.conf: -------------------------------------------------------------------------------- 1 | forward: "kb_w, gb_b"; 2 | back: "kb_s, gb_a"; 3 | left: "kb_a"; 4 | right: "kb_d"; 5 | jump: "kb_space"; 6 | interact: "kb_e"; 7 | 8 | sunDown: "kb_down"; 9 | sunUp: "kb_up"; 10 | sunLeft: "kb_left"; 11 | sunRight: "kb_right"; 12 | 13 | toggleEnvironment: "kb_f1"; 14 | toggleGUI: "kb_g"; 15 | screenshot: "kb_f12"; 16 | -------------------------------------------------------------------------------- /lib/x64/SDL2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gecko0307/wgpu-dlang/3bead903885c3364c2380cea77b720de21542be2/lib/x64/SDL2.dll -------------------------------------------------------------------------------- /lib/x86/SDL2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gecko0307/wgpu-dlang/3bead903885c3364c2380cea77b720de21542be2/lib/x86/SDL2.dll -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gecko0307/wgpu-dlang/3bead903885c3364c2380cea77b720de21542be2/screenshot.jpg -------------------------------------------------------------------------------- /src/dgpu/asset/camera.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.asset.camera; 28 | 29 | import dlib.core.memory; 30 | import dlib.core.ownership; 31 | import dlib.math.vector; 32 | import dlib.math.matrix; 33 | import dlib.math.transformation; 34 | import dlib.math.quaternion; 35 | import dgpu.core.time; 36 | import dgpu.asset.geometry; 37 | 38 | class Camera: Geometry 39 | { 40 | alias viewMatrix = modelMatrix; 41 | mat4 invViewMatrix; 42 | float fov = 60.0f; 43 | float zNear = 0.01f; 44 | float zFar = 1000.0f; 45 | 46 | this(Owner o) 47 | { 48 | super(o); 49 | } 50 | 51 | mat4 projectionMatrix(float aspect) 52 | { 53 | return perspectiveMatrix(fov, aspect, zNear, zFar); 54 | } 55 | 56 | override void update(Time time) 57 | { 58 | super.update(time); 59 | invViewMatrix = viewMatrix.inverse; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/dgpu/asset/entity.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.asset.entity; 28 | 29 | import dlib.core.memory; 30 | import dlib.core.ownership; 31 | import dgpu.asset.geometry; 32 | import dgpu.asset.material; 33 | 34 | class Entity: Owner 35 | { 36 | Geometry geometry; 37 | Material material; 38 | 39 | this(Owner o) 40 | { 41 | super(o); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/dgpu/asset/geometry.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.asset.geometry; 28 | 29 | import dlib.core.memory; 30 | import dlib.core.ownership; 31 | import dlib.math.vector; 32 | import dlib.math.matrix; 33 | import dlib.math.transformation; 34 | import dlib.math.quaternion; 35 | import dlib.math.utils; 36 | import dgpu.core.time; 37 | 38 | class Geometry: Owner 39 | { 40 | vec3 position = vec3(0.0f, 0.0f, 0.0f); 41 | vec3 rotation = vec3(0.0f, 0.0f, 0.0f); 42 | vec3 scale = vec3(1.0f, 1.0f, 1.0f); 43 | Quaternionf orientation = Quaternionf.identity; 44 | mat4 modelMatrix = mat4.identity; 45 | 46 | this(Owner o) 47 | { 48 | super(o); 49 | position = Vector3f(0.0f, 0.0f, 0.0f); 50 | rotation = Quaternionf.identity; 51 | scale = Vector3f(1.0f, 1.0f, 1.0f); 52 | update(Time(0.0, 0.0)); 53 | } 54 | 55 | void update(Time time) 56 | { 57 | orientation = 58 | rotationQuaternion!float(Axis.x, degtorad(rotation.x)) * 59 | rotationQuaternion!float(Axis.y, degtorad(rotation.y)) * 60 | rotationQuaternion!float(Axis.z, degtorad(rotation.z)); 61 | modelMatrix = translationMatrix(position) * orientation.toMatrix4x4 * scaleMatrix(scale); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/dgpu/asset/image.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2024 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.asset.image; 28 | 29 | import dlib.core.memory; 30 | import dlib.core.stream; 31 | import dlib.core.compound; 32 | import dlib.filesystem.local; 33 | import dlib.image.image; 34 | import dlib.image.io; 35 | 36 | SuperImage loadImage(string filename) 37 | { 38 | InputStream input = openForInput(filename); 39 | auto img = loadImage(input); 40 | input.close(); 41 | return img; 42 | } 43 | 44 | SuperImage loadImage(InputStream istrm) 45 | { 46 | Compound!(SuperImage, string) res = 47 | loadImage(istrm, defaultImageFactory); 48 | if (res[0] is null) 49 | throw new Exception(res[1]); 50 | else 51 | return res[0]; 52 | } 53 | 54 | Compound!(SuperImage, string) loadImage( 55 | InputStream istrm, 56 | SuperImageFactory imgFac) 57 | { 58 | ubyte[] data = New!(ubyte[])(istrm.size); 59 | istrm.fillArray(data); 60 | ArrayStream arrStrm = New!ArrayStream(data); 61 | auto res = loadPNG(arrStrm, imgFac); 62 | Delete(arrStrm); 63 | Delete(data); 64 | auto img = res[0]; 65 | if (img.channels != 4) 66 | { 67 | auto img2 = imgFac.createImage(img.width, img.height, 4, 8); 68 | foreach(y; 0..img2.height) 69 | foreach(x; 0..img2.width) 70 | { 71 | img2[x, y] = img[x, y]; 72 | } 73 | img.free(); 74 | res[0] = img2; 75 | } 76 | return res; 77 | } 78 | -------------------------------------------------------------------------------- /src/dgpu/asset/io/obj.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.asset.io.obj; 28 | 29 | import std.stdio; 30 | import std.string; 31 | import std.format; 32 | 33 | import dlib.core.memory; 34 | import dlib.core.stream; 35 | import dlib.core.compound; 36 | import dlib.math.vector; 37 | import dlib.filesystem.stdfs; 38 | import dlib.text.str; 39 | import dgpu.asset.trimesh; 40 | 41 | struct OBJFace 42 | { 43 | uint[3] v; 44 | uint[3] t; 45 | uint[3] n; 46 | } 47 | 48 | Compound!(TriangleMesh, string) loadOBJ(InputStream istrm) 49 | { 50 | TriangleMesh mesh = null; 51 | 52 | Compound!(TriangleMesh, string) error(string errorMsg) 53 | { 54 | if (mesh) 55 | { 56 | Delete(mesh); 57 | mesh = null; 58 | } 59 | return compound(mesh, errorMsg); 60 | } 61 | 62 | uint numVerts = 0; 63 | uint numNormals = 0; 64 | uint numTexcoords = 0; 65 | uint numFaces = 0; 66 | 67 | String fileStr = String(istrm); 68 | 69 | foreach(line; lineSplitter(fileStr)) 70 | { 71 | if (line.startsWith("v ")) 72 | numVerts++; 73 | else if (line.startsWith("vn ")) 74 | numNormals++; 75 | else if (line.startsWith("vt ")) 76 | numTexcoords++; 77 | else if (line.startsWith("f ")) 78 | numFaces++; 79 | } 80 | 81 | vec3[] tmpVertices; 82 | vec3[] tmpNormals; 83 | vec2[] tmpTexcoords; 84 | OBJFace[] tmpFaces; 85 | 86 | bool needGenNormals = false; 87 | 88 | if (!numVerts) 89 | writeln("loadOBJ: file has no vertices"); 90 | if (!numNormals) 91 | { 92 | writeln("loadOBJ: file has no normals (they will be generated)"); 93 | numNormals = numVerts; 94 | needGenNormals = true; 95 | } 96 | if (!numTexcoords) 97 | { 98 | writeln("loadOBJ: file has no texcoords"); 99 | numTexcoords = numVerts; 100 | } 101 | 102 | if (numVerts) 103 | tmpVertices = New!(vec3[])(numVerts); 104 | if (numNormals) 105 | tmpNormals = New!(vec3[])(numNormals); 106 | if (numTexcoords) 107 | tmpTexcoords = New!(vec2[])(numTexcoords); 108 | if (numFaces) 109 | tmpFaces = New!(OBJFace[])(numFaces); 110 | 111 | tmpVertices[] = vec3(0.0f, 0.0f, 0.0f); 112 | tmpNormals[] = vec3(0.0f, 0.0f, 0.0f); 113 | tmpTexcoords[] = vec2(0.0f, 0.0f); 114 | 115 | float x, y, z; 116 | uint v1, v2, v3, v4; 117 | uint t1, t2, t3, t4; 118 | uint n1, n2, n3, n4; 119 | uint vi = 0; 120 | uint ni = 0; 121 | uint ti = 0; 122 | uint fi = 0; 123 | 124 | bool warnAboutQuads = false; 125 | 126 | foreach(line; lineSplitter(fileStr)) 127 | { 128 | if (line.startsWith("v ")) 129 | { 130 | if (formattedRead(line, "v %s %s %s", &x, &y, &z)) 131 | { 132 | tmpVertices[vi] = Vector3f(x, y, z); 133 | vi++; 134 | } 135 | } 136 | else if (line.startsWith("vn")) 137 | { 138 | if (formattedRead(line, "vn %s %s %s", &x, &y, &z)) 139 | { 140 | tmpNormals[ni] = Vector3f(x, y, z); 141 | ni++; 142 | } 143 | } 144 | else if (line.startsWith("vt")) 145 | { 146 | if (formattedRead(line, "vt %s %s", &x, &y)) 147 | { 148 | tmpTexcoords[ti] = Vector2f(x, -y); 149 | ti++; 150 | } 151 | } 152 | else if (line.startsWith("vp")) 153 | { 154 | } 155 | else if (line.startsWith("f")) 156 | { 157 | char[256] tmpStr; 158 | tmpStr[0..line.length] = line[]; 159 | tmpStr[line.length] = 0; 160 | 161 | if (sscanf(tmpStr.ptr, "f %u/%u/%u %u/%u/%u %u/%u/%u %u/%u/%u", &v1, &t1, &n1, &v2, &t2, &n2, &v3, &t3, &n3, &v4, &t4, &n4) == 12) 162 | { 163 | tmpFaces[fi].v[0] = v1-1; 164 | tmpFaces[fi].v[1] = v2-1; 165 | tmpFaces[fi].v[2] = v3-1; 166 | 167 | tmpFaces[fi].t[0] = t1-1; 168 | tmpFaces[fi].t[1] = t2-1; 169 | tmpFaces[fi].t[2] = t3-1; 170 | 171 | tmpFaces[fi].n[0] = n1-1; 172 | tmpFaces[fi].n[1] = n2-1; 173 | tmpFaces[fi].n[2] = n3-1; 174 | 175 | fi++; 176 | 177 | warnAboutQuads = true; 178 | } 179 | if (sscanf(tmpStr.ptr, "f %u/%u/%u %u/%u/%u %u/%u/%u", &v1, &t1, &n1, &v2, &t2, &n2, &v3, &t3, &n3) == 9) 180 | { 181 | tmpFaces[fi].v[0] = v1-1; 182 | tmpFaces[fi].v[1] = v2-1; 183 | tmpFaces[fi].v[2] = v3-1; 184 | 185 | tmpFaces[fi].t[0] = t1-1; 186 | tmpFaces[fi].t[1] = t2-1; 187 | tmpFaces[fi].t[2] = t3-1; 188 | 189 | tmpFaces[fi].n[0] = n1-1; 190 | tmpFaces[fi].n[1] = n2-1; 191 | tmpFaces[fi].n[2] = n3-1; 192 | 193 | fi++; 194 | } 195 | else if (sscanf(tmpStr.ptr, "f %u//%u %u//%u %u//%u %u//%u", &v1, &n1, &v2, &n2, &v3, &n3, &v4, &n4) == 8) 196 | { 197 | tmpFaces[fi].v[0] = v1-1; 198 | tmpFaces[fi].v[1] = v2-1; 199 | tmpFaces[fi].v[2] = v3-1; 200 | 201 | tmpFaces[fi].n[0] = n1-1; 202 | tmpFaces[fi].n[1] = n2-1; 203 | tmpFaces[fi].n[2] = n3-1; 204 | 205 | fi++; 206 | 207 | warnAboutQuads = true; 208 | } 209 | else if (sscanf(tmpStr.ptr, "f %u/%u %u/%u %u/%u", &v1, &t1, &v2, &t2, &v3, &t3) == 6) 210 | { 211 | tmpFaces[fi].v[0] = v1-1; 212 | tmpFaces[fi].v[1] = v2-1; 213 | tmpFaces[fi].v[2] = v3-1; 214 | 215 | tmpFaces[fi].t[0] = t1-1; 216 | tmpFaces[fi].t[1] = t2-1; 217 | tmpFaces[fi].t[2] = t3-1; 218 | 219 | fi++; 220 | } 221 | else if (sscanf(tmpStr.ptr, "f %u//%u %u//%u %u//%u", &v1, &n1, &v2, &n2, &v3, &n3) == 6) 222 | { 223 | tmpFaces[fi].v[0] = v1-1; 224 | tmpFaces[fi].v[1] = v2-1; 225 | tmpFaces[fi].v[2] = v3-1; 226 | 227 | tmpFaces[fi].n[0] = n1-1; 228 | tmpFaces[fi].n[1] = n2-1; 229 | tmpFaces[fi].n[2] = n3-1; 230 | 231 | fi++; 232 | } 233 | else if (sscanf(tmpStr.ptr, "f %u %u %u %u", &v1, &v2, &v3, &v4) == 4) 234 | { 235 | tmpFaces[fi].v[0] = v1-1; 236 | tmpFaces[fi].v[1] = v2-1; 237 | tmpFaces[fi].v[2] = v3-1; 238 | 239 | fi++; 240 | 241 | warnAboutQuads = true; 242 | } 243 | else if (sscanf(tmpStr.ptr, "f %u %u %u", &v1, &v2, &v3) == 3) 244 | { 245 | tmpFaces[fi].v[0] = v1-1; 246 | tmpFaces[fi].v[1] = v2-1; 247 | tmpFaces[fi].v[2] = v3-1; 248 | 249 | fi++; 250 | } 251 | else 252 | assert(0); 253 | } 254 | } 255 | 256 | fileStr.free(); 257 | 258 | if (warnAboutQuads) 259 | writeln("loadOBJ: file includes quads, but Dagon supports only triangles"); 260 | 261 | uint numUniqueVerts = cast(uint)tmpFaces.length * 3; 262 | 263 | mesh = New!TriangleMesh(numUniqueVerts, tmpFaces.length); 264 | 265 | uint index = 0; 266 | 267 | foreach(i, ref OBJFace f; tmpFaces) 268 | { 269 | if (numVerts) 270 | { 271 | mesh.vertices[index] = tmpVertices[f.v[0]]; 272 | mesh.vertices[index+1] = tmpVertices[f.v[1]]; 273 | mesh.vertices[index+2] = tmpVertices[f.v[2]]; 274 | } 275 | else 276 | { 277 | mesh.vertices[index] = Vector3f(0, 0, 0); 278 | mesh.vertices[index+1] = Vector3f(0, 0, 0); 279 | mesh.vertices[index+2] = Vector3f(0, 0, 0); 280 | } 281 | 282 | if (numNormals) 283 | { 284 | mesh.normals[index] = tmpNormals[f.n[0]]; 285 | mesh.normals[index+1] = tmpNormals[f.n[1]]; 286 | mesh.normals[index+2] = tmpNormals[f.n[2]]; 287 | } 288 | else 289 | { 290 | mesh.normals[index] = Vector3f(0, 0, 0); 291 | mesh.normals[index+1] = Vector3f(0, 0, 0); 292 | mesh.normals[index+2] = Vector3f(0, 0, 0); 293 | } 294 | 295 | if (numTexcoords) 296 | { 297 | mesh.texcoords[index] = tmpTexcoords[f.t[0]]; 298 | mesh.texcoords[index+1] = tmpTexcoords[f.t[1]]; 299 | mesh.texcoords[index+2] = tmpTexcoords[f.t[2]]; 300 | } 301 | else 302 | { 303 | mesh.texcoords[index] = Vector2f(0, 0); 304 | mesh.texcoords[index+1] = Vector2f(0, 0); 305 | mesh.texcoords[index+2] = Vector2f(0, 0); 306 | } 307 | 308 | mesh.indices[i * 3] = index; 309 | mesh.indices[i * 3 + 1] = index + 1; 310 | mesh.indices[i * 3 + 2] = index + 2; 311 | 312 | index += 3; 313 | } 314 | 315 | /* 316 | if (needGenNormals) 317 | mesh.generateNormals(); 318 | */ 319 | 320 | if (tmpVertices.length) 321 | Delete(tmpVertices); 322 | if (tmpNormals.length) 323 | Delete(tmpNormals); 324 | if (tmpTexcoords.length) 325 | Delete(tmpTexcoords); 326 | if (tmpFaces.length) 327 | Delete(tmpFaces); 328 | 329 | //mesh.calcBoundingBox(); 330 | 331 | return compound(mesh, ""); 332 | } 333 | -------------------------------------------------------------------------------- /src/dgpu/asset/material.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.asset.material; 28 | 29 | import dlib.core.memory; 30 | import dlib.core.ownership; 31 | import dlib.image.color; 32 | import dlib.math.vector; 33 | import bindbc.wgpu; 34 | import dgpu.asset.texture; 35 | 36 | class Material: Owner 37 | { 38 | Texture baseColorTexture; 39 | vec4 baseColorFactor; 40 | Texture normalTexture; 41 | Texture roughnessMetallicTexture; 42 | vec4 roughnessMetallicFactor; 43 | 44 | this(Owner o) 45 | { 46 | super(o); 47 | baseColorFactor = vec4(1.0f, 1.0f, 1.0f, 1.0f); 48 | baseColorTexture = null; 49 | normalTexture = null; 50 | roughnessMetallicFactor = vec4(0.0f, 0.5f, 0.0f, 0.0f); 51 | roughnessMetallicTexture = null; 52 | } 53 | 54 | float roughnessFactor() @property 55 | { 56 | return roughnessMetallicFactor.y; 57 | } 58 | void roughnessFactor(float factor) @property 59 | { 60 | roughnessMetallicFactor.y = factor; 61 | } 62 | 63 | float metallicFactor() @property 64 | { 65 | return roughnessMetallicFactor.z; 66 | } 67 | void metallicFactor(float factor) @property 68 | { 69 | roughnessMetallicFactor.z = factor; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/dgpu/asset/mipmap.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2023-2025 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.asset.mipmap; 28 | 29 | import std.stdio; 30 | import std.file; 31 | import std.string; 32 | import std.math; 33 | import std.algorithm; 34 | import std.conv; 35 | 36 | import bindbc.wgpu; 37 | import dgpu.core.gpu; 38 | import dgpu.asset.texture; 39 | 40 | string mipmapVertModulePath = "data/shaders/mipmap.vert.spv"; 41 | string mipmapFragModulePath = "data/shaders/mipmap.frag.spv"; 42 | 43 | void generateMipmap(GPU gpu, Texture texture) 44 | { 45 | WGPUSamplerDescriptor samplerDesc = { 46 | label: "mip", 47 | addressModeU: WGPUAddressMode.ClampToEdge, 48 | addressModeV: WGPUAddressMode.ClampToEdge, 49 | addressModeW: WGPUAddressMode.ClampToEdge, 50 | minFilter: WGPUFilterMode.Linear, 51 | magFilter: WGPUFilterMode.Nearest, 52 | mipmapFilter: WGPUMipmapFilterMode.Nearest, 53 | lodMinClamp: 0.0f, 54 | lodMaxClamp: 1.0f, 55 | maxAnisotropy: 1 56 | }; 57 | 58 | WGPUSampler sampler = wgpuDeviceCreateSampler(gpu.device, &samplerDesc); 59 | 60 | WGPUPrimitiveState primitiveStateDesc = { 61 | topology: WGPUPrimitiveTopology.TriangleStrip, 62 | stripIndexFormat: WGPUIndexFormat.Uint32, 63 | frontFace: WGPUFrontFace.CCW, 64 | cullMode: WGPUCullMode.None 65 | }; 66 | 67 | WGPUBlendState blendState = { 68 | color: { 69 | srcFactor: WGPUBlendFactor.One, 70 | dstFactor: WGPUBlendFactor.Zero, 71 | operation: WGPUBlendOperation.Add 72 | }, 73 | alpha: { 74 | srcFactor: WGPUBlendFactor.One, 75 | dstFactor: WGPUBlendFactor.Zero, 76 | operation: WGPUBlendOperation.Add 77 | } 78 | }; 79 | 80 | WGPUColorTargetState colorTargetState = { 81 | format: texture.format, 82 | blend: &blendState, 83 | writeMask: WGPUColorWriteMask.All, 84 | }; 85 | 86 | WGPUBindGroupLayoutEntry[2] bindGroupLayoutEntries = [ 87 | { 88 | nextInChain: null, 89 | binding: 0, 90 | visibility: WGPUShaderStage.Fragment, 91 | sampler: { 92 | nextInChain: null, 93 | type: WGPUSamplerBindingType.Filtering 94 | } 95 | }, 96 | { 97 | nextInChain: null, 98 | binding: 1, 99 | visibility: WGPUShaderStage.Fragment, 100 | texture: { 101 | nextInChain: null, 102 | sampleType: WGPUTextureSampleType.Float, 103 | viewDimension: WGPUTextureViewDimension.D2, 104 | multisampled: false 105 | } 106 | } 107 | ]; 108 | 109 | WGPUBindGroupLayoutDescriptor bindGroupLayoutDescriptor = { 110 | label: "MipmapBindGroupLayout", 111 | entries: bindGroupLayoutEntries.ptr, 112 | entryCount: bindGroupLayoutEntries.length 113 | }; 114 | WGPUBindGroupLayout bindGroupLayout = wgpuDeviceCreateBindGroupLayout(gpu.device, &bindGroupLayoutDescriptor); 115 | 116 | WGPUBindGroupLayout[1] bindGroupLayouts = [ bindGroupLayout ]; 117 | WGPUPipelineLayoutDescriptor pipelineLayoutDesc = { 118 | bindGroupLayouts: bindGroupLayouts.ptr, 119 | bindGroupLayoutCount: bindGroupLayouts.length 120 | }; 121 | 122 | WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(gpu.device, &pipelineLayoutDesc); 123 | 124 | // TODO: load code once and reuse 125 | uint[] spvVert = cast(uint[])read(mipmapVertModulePath); 126 | uint[] spvFrag = cast(uint[])read(mipmapFragModulePath); 127 | 128 | // Vertex shader module 129 | WGPUShaderModuleSPIRVDescriptor spvVertexModuleDescriptor = { 130 | chain: { 131 | next: null, 132 | sType: WGPUSType.ShaderModuleSPIRVDescriptor 133 | }, 134 | codeSize: cast(uint)spvVert.length, 135 | code: spvVert.ptr 136 | }; 137 | WGPUShaderModuleDescriptor vertexShaderModuleDescriptor = { 138 | nextInChain: cast(const(WGPUChainedStruct)*)&spvVertexModuleDescriptor, 139 | label: toStringz("mipmap.vert.spv"), 140 | }; 141 | WGPUShaderModule vertexShaderModule = wgpuDeviceCreateShaderModule(gpu.device, &vertexShaderModuleDescriptor); 142 | 143 | // Fragment shader module 144 | WGPUShaderModuleSPIRVDescriptor spvFragmentModuleDescriptor = { 145 | chain: { 146 | next: null, 147 | sType: WGPUSType.ShaderModuleSPIRVDescriptor 148 | }, 149 | codeSize: cast(uint)spvFrag.length, 150 | code: spvFrag.ptr 151 | }; 152 | WGPUShaderModuleDescriptor fragmentShaderModuleDescriptor = { 153 | nextInChain: cast(const(WGPUChainedStruct)*)&spvFragmentModuleDescriptor, 154 | label: toStringz("mipmap.frag.spv"), 155 | }; 156 | WGPUShaderModule fragmentShaderModule = wgpuDeviceCreateShaderModule(gpu.device, &fragmentShaderModuleDescriptor); 157 | 158 | WGPUFragmentState fragmentState = { 159 | module_: fragmentShaderModule, 160 | entryPoint: "main", 161 | targetCount: 1, 162 | targets: &colorTargetState 163 | }; 164 | 165 | WGPURenderPipelineDescriptor pipelineDesc = { 166 | label: "Mipmap pipeline", 167 | layout: pipelineLayout, 168 | vertex: { 169 | module_: vertexShaderModule, 170 | entryPoint: "main", 171 | bufferCount: 0, 172 | buffers: null 173 | }, 174 | primitive: { 175 | topology: WGPUPrimitiveTopology.TriangleStrip, 176 | stripIndexFormat: WGPUIndexFormat.Uint32, 177 | frontFace: WGPUFrontFace.CCW, 178 | cullMode: WGPUCullMode.None 179 | }, 180 | multisample: { 181 | count: 1, 182 | mask: ~0, 183 | alphaToCoverageEnabled: false 184 | }, 185 | fragment: &fragmentState, 186 | depthStencil: null 187 | }; 188 | 189 | WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(gpu.device, &pipelineDesc); 190 | 191 | uint mipLevelCount = texture.mipLevels; 192 | 193 | WGPUTextureDescriptor mipTextureDesc = { 194 | size: { 195 | width: cast(uint)ceil(texture.width / 2.0f), 196 | height: cast(uint)ceil(texture.height / 2.0f), 197 | depthOrArrayLayers: 1, 198 | }, 199 | format: texture.format, 200 | usage: WGPUTextureUsage.CopySrc | WGPUTextureUsage.TextureBinding | WGPUTextureUsage.RenderAttachment, 201 | dimension: WGPUTextureDimension.D2, 202 | mipLevelCount: mipLevelCount - 1, 203 | sampleCount: 1, 204 | }; 205 | 206 | WGPUTexture mipTexture = wgpuDeviceCreateTexture(gpu.device, &mipTextureDesc); 207 | 208 | WGPUCommandEncoder cmdEncoder = wgpuDeviceCreateCommandEncoder(gpu.device, null); 209 | 210 | WGPUTextureView[] views = new WGPUTextureView[mipLevelCount]; 211 | WGPUBindGroup[] bindGroups = new WGPUBindGroup[mipLevelCount - 1]; 212 | 213 | // 214 | 215 | WGPUTextureViewDescriptor texViewDescriptor0 = { 216 | label: "src_view", 217 | aspect: WGPUTextureAspect.All, 218 | baseMipLevel: 0, 219 | mipLevelCount: 1, 220 | dimension: WGPUTextureViewDimension.D2, 221 | baseArrayLayer: 0, 222 | arrayLayerCount: 1 223 | }; 224 | views[0] = wgpuTextureCreateView(texture.texture, &texViewDescriptor0); 225 | 226 | foreach (i; 1..mipLevelCount) 227 | { 228 | WGPUTextureViewDescriptor texViewDescriptor = { 229 | label: "dst_view", 230 | aspect: WGPUTextureAspect.All, 231 | baseMipLevel: i - 1, 232 | mipLevelCount: 1, 233 | dimension: WGPUTextureViewDimension.D2, 234 | baseArrayLayer: 0, 235 | arrayLayerCount: 1 236 | }; 237 | views[i] = wgpuTextureCreateView(mipTexture, &texViewDescriptor); 238 | assert(views[i] !is null, "Failed to create texture view " ~ i.to!string); 239 | 240 | WGPURenderPassColorAttachment colorAttachmentDesc = { 241 | view: views[i], 242 | depthSlice: WGPU_DEPTH_SLICE_UNDEFINED, 243 | resolveTarget: null, 244 | loadOp: WGPULoadOp.Clear, 245 | storeOp: WGPUStoreOp.Store, 246 | clearValue: WGPUColor(0.0f, 0.0f, 0.0f, 0.0f) 247 | }; 248 | 249 | WGPURenderPassDescriptor renderPassDescriptor = { 250 | colorAttachmentCount: 1, 251 | colorAttachments: &colorAttachmentDesc, 252 | depthStencilAttachment: null 253 | }; 254 | WGPURenderPassEncoder passEncoder = wgpuCommandEncoderBeginRenderPass(cmdEncoder, &renderPassDescriptor); 255 | 256 | WGPUBindGroupEntry[2] bgEntries = [ 257 | { 258 | binding: 0, 259 | sampler: sampler 260 | }, 261 | { 262 | binding: 1, 263 | textureView: views[i - 1] 264 | } 265 | ]; 266 | 267 | uint bindGroupIndex = i - 1; 268 | 269 | WGPUBindGroupDescriptor bgDesc = { 270 | layout: bindGroupLayout, 271 | entryCount: cast(uint)bgEntries.length, 272 | entries: bgEntries.ptr 273 | }; 274 | 275 | bindGroups[bindGroupIndex] = wgpuDeviceCreateBindGroup(gpu.device, &bgDesc); 276 | 277 | wgpuRenderPassEncoderSetPipeline(passEncoder, pipeline); 278 | wgpuRenderPassEncoderSetBindGroup(passEncoder, 0, bindGroups[bindGroupIndex], 0, null); 279 | wgpuRenderPassEncoderDraw(passEncoder, 3, 1, 0, 0); 280 | wgpuRenderPassEncoderEnd(passEncoder); 281 | wgpuRenderPassEncoderRelease(passEncoder); 282 | } 283 | 284 | WGPUExtent3D mipLevelSize = { 285 | width: cast(uint)ceil(texture.width / 2.0f), 286 | height: cast(uint)ceil(texture.height / 2.0f), 287 | depthOrArrayLayers: 1, 288 | }; 289 | 290 | foreach (i; 1..mipLevelCount-1) 291 | { 292 | WGPUImageCopyTexture copySource = { 293 | texture: mipTexture, 294 | mipLevel: i - 1 295 | }; 296 | 297 | WGPUImageCopyTexture copyDestination = { 298 | texture: texture.texture, 299 | mipLevel: i 300 | }; 301 | 302 | wgpuCommandEncoderCopyTextureToTexture(cmdEncoder, ©Source, ©Destination, &mipLevelSize); 303 | mipLevelSize.width = cast(uint)ceil(mipLevelSize.width / 2.0f); 304 | mipLevelSize.height = cast(uint)ceil(mipLevelSize.height / 2.0f); 305 | } 306 | 307 | gpu.submitCommands(cmdEncoder); 308 | wgpuTextureDestroy(mipTexture); 309 | } 310 | -------------------------------------------------------------------------------- /src/dgpu/asset/scene.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.asset.scene; 28 | 29 | import std.algorithm; 30 | import dlib.core.memory; 31 | import dlib.core.ownership; 32 | import dlib.container.array; 33 | import dgpu.core.time; 34 | import dgpu.asset.entity; 35 | import dgpu.asset.camera; 36 | import dgpu.asset.material; 37 | 38 | class Scene: Owner 39 | { 40 | Array!Material materials; 41 | Array!Entity entities; 42 | Material defaultMaterial; 43 | Camera activeCamera; 44 | 45 | this(Owner o) 46 | { 47 | super(o); 48 | defaultMaterial = createMaterial(); 49 | activeCamera = New!Camera(this); 50 | Entity eCamera = createEntity(); 51 | eCamera.geometry = activeCamera; 52 | } 53 | 54 | Material createMaterial() 55 | { 56 | Material mat = New!Material(this); 57 | materials.append(mat); 58 | return mat; 59 | } 60 | 61 | Entity createEntity() 62 | { 63 | Entity e = New!Entity(this); 64 | entities.append(e); 65 | e.material = defaultMaterial; 66 | return e; 67 | } 68 | 69 | auto entitiesByMaterial(Material material) 70 | { 71 | return entities.data.filter!(e => e.material is material); 72 | } 73 | 74 | void update(Time t) 75 | { 76 | foreach(e; entities) 77 | { 78 | if (e.geometry) 79 | e.geometry.update(t); 80 | } 81 | } 82 | 83 | ~this() 84 | { 85 | materials.free(); 86 | entities.free(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/dgpu/asset/texture.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2025 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.asset.texture; 28 | 29 | import std.stdio; 30 | import std.conv; 31 | import std.string; 32 | import std.math; 33 | import std.algorithm; 34 | import core.stdc.string: memcpy; 35 | import dlib.core.ownership; 36 | import dlib.core.memory; 37 | import dlib.image.image; 38 | import bindbc.wgpu; 39 | import dgpu.core.gpu; 40 | import dgpu.asset.mipmap; 41 | 42 | class Texture: Owner 43 | { 44 | GPU gpu; 45 | uint width; 46 | uint height; 47 | uint mipLevels; 48 | uint numLayers; 49 | WGPUTextureFormat format; 50 | WGPUTexture texture; 51 | WGPUTextureView view; 52 | WGPUSampler sampler; 53 | string label; 54 | 55 | this(GPU gpu, SuperImage[] images, Owner owner) 56 | { 57 | super(owner); 58 | label = toHash.to!string; 59 | this.gpu = gpu; 60 | init(images); 61 | generateMipmap(gpu, this); 62 | } 63 | 64 | this(GPU gpu, SuperImage img, Owner owner) 65 | { 66 | this(gpu, [img], owner); 67 | } 68 | 69 | protected void init(SuperImage[] images) 70 | { 71 | numLayers = cast(uint)images.length; 72 | 73 | this.width = images[0].width; 74 | this.height = images[0].height; 75 | 76 | this.format = WGPUTextureFormat.RGBA8Unorm; 77 | 78 | this.mipLevels = cast(uint)log2(cast(double)max(width, height)) + 1; 79 | 80 | WGPUTextureDescriptor textureDescriptor = 81 | { 82 | nextInChain: null, 83 | label: label.toStringz, 84 | usage: WGPUTextureUsage.TextureBinding | WGPUTextureUsage.CopyDst, 85 | dimension: WGPUTextureDimension.D2, 86 | size: WGPUExtent3D(width, height, numLayers), 87 | format: format, 88 | mipLevelCount: mipLevels, 89 | sampleCount: 1 90 | }; 91 | texture = wgpuDeviceCreateTexture(gpu.device, &textureDescriptor); 92 | 93 | WGPUTextureViewDescriptor textureViewDescriptor = 94 | { 95 | nextInChain: null, 96 | label: label.toStringz, 97 | format: format, 98 | dimension: WGPUTextureViewDimension.D2Array, 99 | baseMipLevel: 0, 100 | mipLevelCount: mipLevels, 101 | baseArrayLayer: 0, 102 | arrayLayerCount: numLayers, 103 | aspect: WGPUTextureAspect.All 104 | }; 105 | view = wgpuTextureCreateView(texture, &textureViewDescriptor); 106 | 107 | foreach(layer, img; images) 108 | { 109 | layerFromImage(img, cast(uint)layer); 110 | } 111 | 112 | WGPUSamplerDescriptor samplerDescriptor = 113 | { 114 | nextInChain: null, 115 | label: label.toStringz, 116 | addressModeU: WGPUAddressMode.Repeat, 117 | addressModeV: WGPUAddressMode.Repeat, 118 | addressModeW: WGPUAddressMode.Repeat, 119 | magFilter: WGPUFilterMode.Linear, 120 | minFilter: WGPUFilterMode.Linear, 121 | mipmapFilter: WGPUMipmapFilterMode.Linear, 122 | lodMinClamp: 0.0f, 123 | lodMaxClamp: mipLevels, 124 | compare: WGPUCompareFunction.Undefined, 125 | maxAnisotropy: 1 126 | }; 127 | sampler = wgpuDeviceCreateSampler(gpu.device, &samplerDescriptor); 128 | } 129 | 130 | void layerFromImage(SuperImage img, uint layer) 131 | { 132 | if (img.channels != 4 || img.bitDepth != 8) 133 | { 134 | writeln("Warning: only RGBA8 images are supported"); 135 | return; 136 | } 137 | 138 | if (img.width != width || img.height != height) 139 | { 140 | writeln("Warning: image and texture sizes mismatch"); 141 | return; 142 | } 143 | 144 | WGPUBufferDescriptor textureBufferDescriptor = { 145 | nextInChain: null, 146 | label: ("TextureBuffer_" ~ label).toStringz, 147 | usage: WGPUBufferUsage.Storage | WGPUBufferUsage.CopyDst | WGPUBufferUsage.CopySrc, 148 | size: img.data.length, 149 | mappedAtCreation: true 150 | }; 151 | 152 | WGPUBuffer textureBuffer = wgpuDeviceCreateBuffer(gpu.device, &textureBufferDescriptor); 153 | void* bufferPtr = wgpuBufferGetMappedRange(textureBuffer, 0, img.data.length); 154 | memcpy(bufferPtr, img.data.ptr, img.data.length); 155 | wgpuBufferUnmap(textureBuffer); 156 | 157 | WGPUCommandEncoderDescriptor commandEncoderDescriptor = { 158 | nextInChain: null, 159 | label: ("CommandEncoder_" ~ label).toStringz, 160 | }; 161 | WGPUCommandEncoder commandEncoder = wgpuDeviceCreateCommandEncoder(gpu.device, &commandEncoderDescriptor); 162 | 163 | WGPUImageCopyBuffer srcCopyBuffer = { 164 | nextInChain: null, 165 | layout: { 166 | nextInChain: null, 167 | offset: 0, 168 | bytesPerRow: width * 4, 169 | rowsPerImage: height 170 | }, 171 | buffer: textureBuffer 172 | }; 173 | WGPUImageCopyTexture dstCopyTexture = { 174 | nextInChain: null, 175 | texture: texture, 176 | mipLevel: 0, 177 | origin: WGPUOrigin3D(0, 0, layer), 178 | aspect: WGPUTextureAspect.All 179 | }; 180 | auto copySize = WGPUExtent3D(width, height, 1); 181 | 182 | wgpuCommandEncoderCopyBufferToTexture(commandEncoder, &srcCopyBuffer, &dstCopyTexture, ©Size); 183 | 184 | WGPUCommandBufferDescriptor commandBufferDescriptor = { 185 | nextInChain: null, 186 | label: ("CommandBuffer_" ~ label).toStringz 187 | }; 188 | WGPUCommandBuffer commandBuffer = wgpuCommandEncoderFinish(commandEncoder, &commandBufferDescriptor); 189 | wgpuQueueSubmit(gpu.queue, 1, &commandBuffer); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/dgpu/asset/trimesh.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.asset.trimesh; 28 | 29 | import dlib.core.memory; 30 | import dlib.math.vector; 31 | 32 | class TriangleMesh 33 | { 34 | vec3[] vertices; 35 | vec2[] texcoords; 36 | vec3[] normals; 37 | uint[] indices; 38 | 39 | this(size_t numVertices, size_t numFaces) 40 | { 41 | vertices = New!(vec3[])(numVertices); 42 | texcoords = New!(vec2[])(numVertices); 43 | normals = New!(vec3[])(numVertices); 44 | indices = New!(uint[])(numFaces * 3); 45 | } 46 | 47 | ~this() 48 | { 49 | Delete(vertices); 50 | Delete(texcoords); 51 | Delete(normals); 52 | Delete(indices); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/dgpu/core/application.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | /++ 29 | Base class to inherit applications from. 30 | +/ 31 | module dgpu.core.application; 32 | 33 | import std.stdio; 34 | import std.conv; 35 | import std.getopt; 36 | import std.string; 37 | import std.file; 38 | import std.algorithm: canFind; 39 | 40 | import bindbc.sdl; 41 | import bindbc.wgpu; 42 | import sharedlibLoader = bindbc.loader.sharedlib; 43 | 44 | import dlib.core.memory; 45 | import dlib.filesystem.stdfs; 46 | import dgpu.core.event; 47 | import dgpu.core.time; 48 | import dgpu.core.logger; 49 | import dgpu.core.gpu; 50 | 51 | enum AppEvent 52 | { 53 | Exit = -1 54 | } 55 | 56 | /++ 57 | Base class to inherit applications from. 58 | `Application` wraps SDL2 window, loads dynamic link libraries using BindBC, 59 | is responsible for initializing OpenGL context and doing main game loop. 60 | +/ 61 | class Application: EventListener 62 | { 63 | uint width; 64 | uint height; 65 | SDL_Window* window = null; 66 | private EventManager _eventManager; 67 | private Logger _logger; 68 | Logger logger; 69 | Cadencer cadencer; 70 | GPU gpu; 71 | StdFileSystem fs; 72 | 73 | /++ 74 | Constructor. 75 | * `winWidth` - window width 76 | * `winHeight` - window height 77 | * `fullscreen` - if true, the application will run in fullscreen mode 78 | * `windowTitle` - window title 79 | * `args` - command line arguments 80 | * `logger` - optional custom logger to print messages 81 | +/ 82 | this(uint winWidth, uint winHeight, bool fullscreen, string windowTitle, string[] args, Logger userLogger = null) 83 | { 84 | fs = New!StdFileSystem(); 85 | 86 | _logger = New!Logger(this); 87 | if (userLogger) 88 | logger = userLogger; 89 | else 90 | logger = _logger; 91 | 92 | SDLSupport sdlSup = loadSDL(); 93 | if (sdlSup != sdlSupport) 94 | { 95 | if (sdlSup == SDLSupport.badLibrary) 96 | logger.log("Failed to load some SDL functions. It seems that you have an old version of SDL. The engine will try to use it, but it is recommended to install SDL 2.0.14 or higher"); 97 | else 98 | logger.error("SDL library is not found. Please, install SDL 2.0.14 or higher"); 99 | } 100 | 101 | WGPUSupport wgpuSup = loadWGPU(); 102 | if (wgpuSup != WGPUSupport.wgpu022) 103 | { 104 | if (wgpuSup == WGPUSupport.badLibrary) 105 | logger.log("Failed to load some WGPU functions"); 106 | else 107 | logger.error("WGPU library is not found"); 108 | } 109 | 110 | if (sharedlibLoader.errors.length) 111 | { 112 | logger.log("Loader errors:"); 113 | foreach(info; sharedlibLoader.errors) 114 | { 115 | logger.log(to!string(info.error) ~ ": " ~ to!string(info.message)); 116 | } 117 | } 118 | 119 | version(OSX) 120 | { 121 | SDL_SetHint(SDL_HINT_RENDER_DRIVER, toStringz("metal")); 122 | } 123 | 124 | if (SDL_Init(SDL_INIT_EVERYTHING) != 0) 125 | logger.error("Failed to init SDL: " ~ to!string(SDL_GetError())); 126 | 127 | width = winWidth; 128 | height = winHeight; 129 | 130 | window = SDL_CreateWindow(toStringz(windowTitle), 131 | SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 132 | width, height, 133 | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); 134 | if (window is null) 135 | logger.error("Failed to create window: " ~ to!string(SDL_GetError())); 136 | 137 | if (fullscreen) 138 | SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN); 139 | 140 | gpu = New!GPU(this); 141 | 142 | _eventManager = New!EventManager(window, width, height); 143 | super(_eventManager, null); 144 | 145 | cadencer = New!Cadencer(&onAnimationFrame, 60, this); 146 | } 147 | 148 | ~this() 149 | { 150 | Delete(fs); 151 | SDL_DestroyWindow(window); 152 | SDL_Quit(); 153 | Delete(_eventManager); 154 | } 155 | 156 | void maximizeWindow() 157 | { 158 | SDL_MaximizeWindow(window); 159 | } 160 | 161 | override void onUserEvent(int code) 162 | { 163 | if (code == AppEvent.Exit) 164 | { 165 | exit(); 166 | } 167 | } 168 | 169 | void onUpdate(Time t) 170 | { 171 | // Override me 172 | } 173 | 174 | void onRender() 175 | { 176 | // Override me 177 | } 178 | 179 | void onAnimationFrame(Time t) 180 | { 181 | eventManager.update(); 182 | processEvents(); 183 | 184 | // wgpu crashes when rendering to minimized window 185 | auto winFlags = SDL_GetWindowFlags(window); 186 | auto isMinimized = winFlags & SDL_WINDOW_MINIMIZED; 187 | if (!isMinimized) 188 | { 189 | onUpdate(t); 190 | onRender(); 191 | } 192 | } 193 | 194 | void run() 195 | { 196 | Time t = Time(0.0, 0.0); 197 | while(eventManager.running) 198 | { 199 | eventManager.updateTimer(); 200 | t.delta = eventManager.deltaTime; 201 | cadencer.update(t); 202 | t.elapsed += t.delta; 203 | } 204 | } 205 | 206 | void exit() 207 | { 208 | eventManager.exit(); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/dgpu/core/config.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | module dgpu.core.config; 29 | 30 | import std.stdio; 31 | import std.process; 32 | import std.string; 33 | import dlib.core.memory; 34 | import dlib.core.ownership; 35 | import dlib.filesystem.filesystem; 36 | import dlib.filesystem.stdfs; 37 | import dgpu.core.vfs; 38 | import dgpu.core.props; 39 | 40 | class Configuration: Owner 41 | { 42 | protected: 43 | VirtualFileSystem fs; 44 | 45 | public: 46 | Properties props; 47 | 48 | this(Owner o) 49 | { 50 | super(o); 51 | 52 | fs = New!VirtualFileSystem(); 53 | fs.mount("."); 54 | 55 | string homeDirVar = ""; 56 | version(Windows) homeDirVar = "APPDATA"; 57 | version(Posix) homeDirVar = "HOME"; 58 | auto homeDir = environment.get(homeDirVar, ""); 59 | if (homeDir.length) 60 | { 61 | string appdataDir = format("%s/.dagon", homeDir); 62 | fs.mount(appdataDir); 63 | } 64 | 65 | props = New!Properties(this); 66 | } 67 | 68 | ~this() 69 | { 70 | Delete(fs); 71 | } 72 | 73 | bool fromFile(string filename) 74 | { 75 | FileStat stat; 76 | if (fs.stat(filename, stat)) 77 | { 78 | auto istrm = fs.openForInput(filename); 79 | auto input = readText(istrm); 80 | Delete(istrm); 81 | props.parse(input); 82 | Delete(input); 83 | return true; 84 | } 85 | else 86 | return false; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/dgpu/core/gpu.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.core.gpu; 28 | 29 | import std.stdio; 30 | import std.conv; 31 | import bindbc.sdl; 32 | import bindbc.wgpu; 33 | import dlib.core.ownership; 34 | import dlib.container.array; 35 | import dgpu.core.application; 36 | 37 | class GPU: Owner 38 | { 39 | Application application; 40 | WGPUInstance instance; 41 | WGPUSurface surface; 42 | WGPUAdapter adapter; 43 | WGPUDevice device; 44 | WGPUQueue queue; 45 | WGPUAdapterInfo adapterInfo; 46 | 47 | this(Application app) 48 | { 49 | super(app); 50 | application = app; 51 | 52 | debug 53 | { 54 | WGPULogLevel logLevel = WGPULogLevel.Debug; // WGPULogLevel.Trace 55 | } 56 | else 57 | { 58 | WGPULogLevel logLevel = WGPULogLevel.Warn; 59 | } 60 | wgpuSetLogLevel(logLevel); 61 | wgpuSetLogCallback(&logCallback, null); 62 | 63 | WGPUInstanceDescriptor instanceDesc; 64 | instance = wgpuCreateInstance(&instanceDesc); 65 | 66 | SDL_SysWMinfo wmInfo; 67 | SDL_VERSION(&wmInfo.version_); 68 | if (SDL_GetWindowWMInfo(app.window, &wmInfo) != SDL_TRUE) 69 | app.logger.error("Failed to init SDL: " ~ to!string(SDL_GetError())); 70 | 71 | app.logger.log("Subsystem: " ~ to!string(wmInfo.subsystem)); 72 | surface = createSurface(wmInfo); 73 | app.logger.log("Surface created"); 74 | adapter = createAdapter(surface); 75 | app.logger.log("Adapter created"); 76 | device = createDevice(adapter); 77 | app.logger.log("Device created"); 78 | queue = wgpuDeviceGetQueue(device); 79 | app.logger.log("Queue created"); 80 | 81 | wgpuAdapterGetInfo(adapter, &adapterInfo); 82 | app.logger.log("Device ID: " ~ to!string(adapterInfo.deviceID)); 83 | app.logger.log("Vendor ID: " ~ to!string(adapterInfo.vendorID)); 84 | app.logger.log("Adapter type: " ~ to!string(adapterInfo.adapterType)); 85 | app.logger.log("Backend: " ~ to!string(adapterInfo.backendType)); 86 | } 87 | 88 | protected WGPUAdapter createAdapter(WGPUSurface surface) 89 | { 90 | WGPUAdapter adapter; 91 | WGPURequestAdapterOptions adapterOptions = { 92 | nextInChain: null, 93 | compatibleSurface: surface, 94 | powerPreference: WGPUPowerPreference.HighPerformance 95 | }; 96 | wgpuInstanceRequestAdapter(instance, &adapterOptions, &requestAdapterCallback, cast(void*)&adapter); 97 | return adapter; 98 | } 99 | 100 | protected WGPUDevice createDevice(WGPUAdapter adapter) 101 | { 102 | WGPUDeviceExtras deviceExtras = { 103 | chain: { 104 | next: null, 105 | sType: cast(WGPUSType)WGPUNativeSType.DeviceExtras 106 | }, 107 | tracePath: null, 108 | }; 109 | WGPURequiredLimits limits = { 110 | nextInChain: null, 111 | limits: { 112 | maxTextureDimension1D: 8192, 113 | maxTextureDimension2D: 8192, 114 | maxTextureDimension3D: 2048, 115 | maxTextureArrayLayers: 2048 116 | } 117 | }; 118 | WGPUDeviceDescriptor deviceDescriptor = { 119 | nextInChain: cast(const(WGPUChainedStruct)*)&deviceExtras, 120 | requiredFeatureCount: 0, 121 | requiredFeatures: null, 122 | requiredLimits: &limits 123 | }; 124 | WGPUDevice device = null; 125 | wgpuAdapterRequestDevice(adapter, &deviceDescriptor, &requestDeviceCallback, cast(void*)&device); 126 | return device; 127 | } 128 | 129 | WGPUCommandEncoder createCommandEncoder() 130 | { 131 | WGPUCommandEncoderDescriptor commandEncoderDescriptor = { 132 | label: "Command Encoder" 133 | }; 134 | return wgpuDeviceCreateCommandEncoder(device, &commandEncoderDescriptor); 135 | } 136 | 137 | void submitCommands(WGPUCommandEncoder commandEncoder) 138 | { 139 | WGPUCommandBufferDescriptor commandBufferDescriptor = { label: null }; 140 | WGPUCommandBuffer commandBuffer = wgpuCommandEncoderFinish(commandEncoder, &commandBufferDescriptor); 141 | wgpuQueueSubmit(queue, 1, &commandBuffer); 142 | wgpuCommandBufferRelease(commandBuffer); 143 | } 144 | 145 | protected WGPUSurface createSurface(SDL_SysWMinfo wmInfo) 146 | { 147 | WGPUSurface surface; 148 | version(Windows) 149 | { 150 | if (wmInfo.subsystem == SDL_SYSWM_WINDOWS) 151 | { 152 | auto win_hwnd = wmInfo.info.win.window; 153 | auto win_hinstance = wmInfo.info.win.hinstance; 154 | WGPUSurfaceDescriptorFromWindowsHWND sfdHwnd = { 155 | chain: { 156 | next: null, 157 | sType: WGPUSType.SurfaceDescriptorFromWindowsHWND 158 | }, 159 | hinstance: win_hinstance, 160 | hwnd: win_hwnd 161 | }; 162 | WGPUSurfaceDescriptor sfd = { 163 | label: null, 164 | nextInChain: cast(const(WGPUChainedStruct)*)&sfdHwnd 165 | }; 166 | surface = wgpuInstanceCreateSurface(instance, &sfd); 167 | } 168 | else 169 | { 170 | application.logger.error("Unsupported subsystem, sorry"); 171 | } 172 | } 173 | else version(linux) 174 | { 175 | if (wmInfo.subsystem == SDL_SYSWM_WAYLAND) 176 | { 177 | // TODO: support Wayland 178 | application.logger.error("Unsupported subsystem, sorry"); 179 | } 180 | // System might use XCB so SDL_SysWMinfo will contain subsystem SDL_SYSWM_UNKNOWN. Although, X11 still can be used to create surface 181 | else 182 | { 183 | auto x11_display = wmInfo.info.x11.display; 184 | auto x11_window = wmInfo.info.x11.window; 185 | WGPUSurfaceDescriptorFromXlibWindow sfdX11 = { 186 | chain: { 187 | next: null, 188 | sType: WGPUSType.SurfaceDescriptorFromXlibWindow 189 | }, 190 | display: x11_display, 191 | window: x11_window 192 | }; 193 | WGPUSurfaceDescriptor sfd = { 194 | label: null, 195 | nextInChain: cast(const(WGPUChainedStruct)*)&sfdX11 196 | }; 197 | surface = wgpuInstanceCreateSurface(instance, &sfd); 198 | } 199 | } 200 | else version(OSX) 201 | { 202 | // Needs test! 203 | SDL_Renderer* renderer = SDL_CreateRenderer(window.sdlWindow, -1, SDL_RENDERER_PRESENTVSYNC); 204 | auto metalLayer = SDL_RenderGetMetalLayer(renderer); 205 | 206 | WGPUSurfaceDescriptorFromMetalLayer sfdMetal = { 207 | chain: { 208 | next: null, 209 | sType: WGPUSType.SurfaceDescriptorFromMetalLayer 210 | }, 211 | layer: metalLayer 212 | }; 213 | WGPUSurfaceDescriptor sfd = { 214 | label: null, 215 | nextInChain: cast(const(WGPUChainedStruct)*)&sfdMetal 216 | }; 217 | surface = wgpuInstanceCreateSurface(instance, &sfd); 218 | 219 | SDL_DestroyRenderer(renderer); 220 | } 221 | return surface; 222 | } 223 | } 224 | 225 | private extern(C) 226 | { 227 | void logCallback(WGPULogLevel level, const(char)* msg, void* user_data) 228 | { 229 | const (char)[] level_message; 230 | switch(level) 231 | { 232 | case WGPULogLevel.Off:level_message = "off";break; 233 | case WGPULogLevel.Error:level_message = "error";break; 234 | case WGPULogLevel.Warn:level_message = "warn";break; 235 | case WGPULogLevel.Info:level_message = "info";break; 236 | case WGPULogLevel.Debug:level_message = "debug";break; 237 | case WGPULogLevel.Trace:level_message = "trace";break; 238 | default: level_message = "-"; 239 | } 240 | writeln("WebGPU ", level_message, ": ", to!string(msg)); 241 | } 242 | 243 | void requestAdapterCallback(WGPURequestAdapterStatus status, WGPUAdapter adapter, const(char)* message, void* userdata) 244 | { 245 | if (status == WGPURequestAdapterStatus.Success) 246 | *cast(WGPUAdapter*)userdata = adapter; 247 | else 248 | { 249 | writeln(status); 250 | writeln(to!string(message)); 251 | } 252 | } 253 | 254 | void requestDeviceCallback(WGPURequestDeviceStatus status, WGPUDevice device, const(char)* message, void* userdata) 255 | { 256 | if (status == WGPURequestDeviceStatus.Success) 257 | *cast(WGPUDevice*)userdata = device; 258 | else 259 | { 260 | writeln(status); 261 | writeln(to!string(message)); 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/dgpu/core/input.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2023 Mateusz Muszyński 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | 6 | Permission is hereby granted, free of charge, to any person or organization 7 | obtaining a copy of the software and accompanying documentation covered by 8 | this license (the "Software") to use, reproduce, display, distribute, 9 | execute, and transmit the Software, and to prepare derivative works of the 10 | Software, and to permit third-parties to whom the Software is furnished to 11 | do so, all subject to the following: 12 | 13 | The copyright notices in the Software and this entire statement, including 14 | the above license grant, this restriction and the following disclaimer, 15 | must be included in all copies of the Software, in whole or in part, and 16 | all derivative works of the Software, unless such copies or derivative 17 | works are solely in the form of machine-executable object code generated by 18 | a source language processor. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | */ 28 | 29 | module dgpu.core.input; 30 | 31 | import std.stdio; 32 | import std.ascii; 33 | import std.conv: to; 34 | import std.math: abs; 35 | import std.algorithm.searching: startsWith; 36 | import std.string: isNumeric; 37 | import dlib.core.memory; 38 | import dlib.core.ownership; 39 | import dlib.container.dict; 40 | import dlib.container.array; 41 | import dlib.text.lexer; 42 | import dlib.text.str; 43 | import bindbc.sdl; 44 | import dgpu.core.event; 45 | import dgpu.core.keycodes; 46 | import dgpu.core.config; 47 | 48 | enum BindingType 49 | { 50 | None, 51 | Keyboard, 52 | MouseButton, 53 | MouseAxis, 54 | GamepadButton, 55 | GamepadAxis, 56 | VirtualAxis // axis created from two buttons 57 | } 58 | 59 | struct Binding 60 | { 61 | BindingType type; 62 | union 63 | { 64 | int key; 65 | int button; 66 | int axis; 67 | 68 | private struct Vaxis 69 | { 70 | BindingType typePos; 71 | int pos; 72 | BindingType typeNeg; 73 | int neg; 74 | } 75 | Vaxis vaxis; // virtual axis 76 | } 77 | } 78 | 79 | class InputManager 80 | { 81 | EventManager eventManager; 82 | 83 | alias Bindings = Array!Binding; 84 | 85 | Dict!(Bindings, string) bindings; 86 | 87 | Configuration config; 88 | 89 | this(EventManager em) 90 | { 91 | eventManager = em; 92 | bindings = dict!(Bindings, string)(); 93 | 94 | config = New!Configuration(null); 95 | if (!config.fromFile("input.conf")) 96 | { 97 | writeln("Warning: no \"input.conf\" found"); 98 | } 99 | 100 | foreach(name, value; config.props.props) 101 | { 102 | addBindings(name, value.data); 103 | } 104 | } 105 | 106 | ~this() 107 | { 108 | foreach(name, bind; bindings) 109 | { 110 | bind.free(); 111 | } 112 | Delete(bindings); 113 | Delete(config); 114 | } 115 | 116 | private Binding parseBinding(Lexer lexer) 117 | { 118 | // Binding format consist of device type and name(or number) 119 | // coresponding to button or axis of this device 120 | // eg. kb_up, kb_w, ma_0, mb_1, gb_a, gb_x, ga_leftx, ga_lefttrigger 121 | // kb -> keybaord, by name 122 | // ma -> mouse axis, by number 123 | // mb -> mouse button, by number 124 | // ga -> gamepad axis, by name 125 | // gb -> gamepad button, by name 126 | // va -> virtual axis, special syntax: va(kb_up, kb_down) 127 | 128 | BindingType type = BindingType.None; 129 | int result = -1; 130 | 131 | auto lexeme = lexer.getLexeme(); 132 | 133 | switch(lexeme) 134 | { 135 | case "kb": type = BindingType.Keyboard; break; 136 | case "ma": type = BindingType.MouseAxis; break; 137 | case "mb": type = BindingType.MouseButton; break; 138 | case "ga": type = BindingType.GamepadAxis; break; 139 | case "gb": type = BindingType.GamepadButton; break; 140 | case "va": type = BindingType.VirtualAxis; break; 141 | 142 | default: goto fail; 143 | } 144 | 145 | lexeme = lexer.getLexeme(); 146 | 147 | if (type != BindingType.VirtualAxis) 148 | { 149 | if (lexeme != "_") 150 | goto fail; 151 | 152 | lexeme = lexer.getLexeme(); 153 | 154 | if(lexeme.isNumeric) 155 | { 156 | result = to!int(lexeme); 157 | } 158 | else 159 | { 160 | String svalue = String(lexeme); 161 | const(char)* cvalue = svalue.ptr; 162 | switch(type) 163 | { 164 | case BindingType.Keyboard: result = cast(int)SDL_GetScancodeFromName(cvalue); break; 165 | case BindingType.GamepadAxis: result = cast(int)SDL_GameControllerGetAxisFromString(cvalue); break; 166 | case BindingType.GamepadButton: result = cast(int)SDL_GameControllerGetButtonFromString(cvalue); break; 167 | 168 | default: break; 169 | } 170 | 171 | svalue.free(); 172 | } 173 | 174 | if (type != BindingType.None || result > 0) 175 | return Binding(type, result); 176 | } 177 | else 178 | { 179 | // Virtual axis 180 | if (lexeme != "(") 181 | goto fail; 182 | 183 | Binding pos = parseBinding(lexer); 184 | 185 | if (pos.type != BindingType.Keyboard && 186 | pos.type != BindingType.MouseButton && 187 | pos.type != BindingType.GamepadButton) 188 | goto fail; 189 | 190 | lexeme = lexer.getLexeme(); 191 | if (lexeme != ",") 192 | goto fail; 193 | 194 | Binding neg = parseBinding(lexer); 195 | 196 | if (neg.type != BindingType.Keyboard && 197 | neg.type != BindingType.MouseButton && 198 | neg.type != BindingType.GamepadButton) 199 | goto fail; 200 | 201 | lexeme = lexer.getLexeme(); 202 | 203 | if (lexeme != ")") 204 | goto fail; 205 | 206 | Binding bind = Binding(type); 207 | bind.vaxis.typePos = pos.type; 208 | bind.vaxis.pos = pos.key; 209 | bind.vaxis.typeNeg = neg.type; 210 | bind.vaxis.neg = neg.key; 211 | return bind; 212 | } 213 | 214 | fail: 215 | return Binding(BindingType.None, -1); 216 | } 217 | 218 | void addBindings(string name, string value) 219 | { 220 | auto lexer = New!Lexer(value, ["_", ",", "(", ")"]); 221 | lexer.ignoreWhitespaces = true; 222 | 223 | while(true) 224 | { 225 | Binding b = parseBinding(lexer); 226 | if (b.type == BindingType.None && b.key == -1) 227 | { 228 | writefln("Error: wrong binding format \"%s\"", value); 229 | break; 230 | } 231 | 232 | if (auto binding = name in bindings) 233 | { 234 | binding.insertBack(b); 235 | } 236 | else 237 | { 238 | auto binds = Bindings(); 239 | binds.insertBack(b); 240 | bindings[name] = binds; 241 | } 242 | 243 | auto lexeme = lexer.getLexeme(); 244 | if (lexeme == ",") 245 | continue; 246 | 247 | if (lexeme == "") 248 | break; 249 | } 250 | 251 | Delete(lexer); 252 | } 253 | 254 | void clearBindings(string name) 255 | { 256 | if (auto binding = name in bindings) 257 | { 258 | binding.removeBack(cast(uint)binding.length); 259 | } 260 | } 261 | 262 | bool getButton(Binding binding) 263 | { 264 | switch(binding.type) 265 | { 266 | case BindingType.Keyboard: 267 | if (eventManager.keyPressed[binding.key]) return true; 268 | break; 269 | 270 | case BindingType.MouseButton: 271 | if (eventManager.mouseButtonPressed[binding.button]) return true; 272 | break; 273 | 274 | case BindingType.GamepadButton: 275 | if (eventManager.controllerButtonPressed[binding.button]) return true; 276 | break; 277 | 278 | default: 279 | break; 280 | } 281 | 282 | return false; 283 | } 284 | 285 | bool getButton(string name) 286 | { 287 | auto b = name in bindings; 288 | if (!b) 289 | return false; 290 | 291 | for(int i = 0; i < b.length; i++) 292 | { 293 | if (getButton((*b)[i])) return true; 294 | } 295 | 296 | return false; 297 | } 298 | 299 | bool getButtonUp(string name) 300 | { 301 | auto b = name in bindings; 302 | if (!b) 303 | return false; 304 | 305 | for(int i = 0; i < b.length; i++) 306 | { 307 | auto binding = (*b)[i]; 308 | switch(binding.type) 309 | { 310 | case BindingType.Keyboard: 311 | if (eventManager.keyUp[binding.key]) return true; 312 | break; 313 | 314 | case BindingType.MouseButton: 315 | if (eventManager.mouseButtonUp[binding.button]) return true; 316 | break; 317 | 318 | case BindingType.GamepadButton: 319 | if (eventManager.controllerButtonUp[binding.button]) return true; 320 | break; 321 | 322 | default: 323 | break; 324 | } 325 | } 326 | 327 | return false; 328 | } 329 | 330 | bool getButtonDown(string name) 331 | { 332 | auto b = name in bindings; 333 | if (!b) 334 | return false; 335 | 336 | for(int i = 0; i < b.length; i++) 337 | { 338 | auto binding = (*b)[i]; 339 | 340 | switch(binding.type) 341 | { 342 | case BindingType.Keyboard: 343 | if (eventManager.keyDown[binding.key]) return true; 344 | break; 345 | 346 | case BindingType.MouseButton: 347 | if (eventManager.mouseButtonDown[binding.button]) return true; 348 | break; 349 | 350 | case BindingType.GamepadButton: 351 | if (eventManager.controllerButtonDown[binding.button]) return true; 352 | break; 353 | 354 | default: 355 | break; 356 | } 357 | } 358 | 359 | return false; 360 | } 361 | 362 | float getAxis(string name) 363 | { 364 | auto b = name in bindings; 365 | if (!b) 366 | return false; 367 | 368 | float result = 0.0f; 369 | float aresult = 0.0f; // absolute result 370 | 371 | for(int i = 0; i < b.length; i++) 372 | { 373 | auto binding = (*b)[i]; 374 | float value = 0.0f; 375 | 376 | switch(binding.type) 377 | { 378 | case BindingType.MouseAxis: 379 | if (binding.axis == 0) 380 | value = eventManager.mouseRelX / (eventManager.windowWidth * 0.5f); // map to -1 to 1 range 381 | else if (binding.axis == 1) 382 | value = eventManager.mouseRelY / (eventManager.windowHeight * 0.5f); 383 | break; 384 | 385 | case BindingType.GamepadAxis: 386 | if (eventManager.gameControllerAvailable) 387 | value = eventManager.gameControllerAxis(binding.axis); 388 | break; 389 | 390 | case BindingType.VirtualAxis: 391 | value = getButton(*cast(Binding*)(&binding.vaxis.typePos)) ? 1.0f : 0.0f; 392 | value += getButton(*cast(Binding*)(&binding.vaxis.typeNeg)) ? -1.0f : 0.0f; 393 | break; 394 | 395 | default: 396 | break; 397 | } 398 | float avalue = abs(value); 399 | if (avalue > aresult) 400 | { 401 | result = value; 402 | aresult = avalue; 403 | } 404 | } 405 | 406 | return result; 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /src/dgpu/core/keycodes.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | module dgpu.core.keycodes; 29 | 30 | import bindbc.sdl; 31 | 32 | enum 33 | { 34 | KEY_UNKNOWN = 0, 35 | 36 | KEY_A = 4, 37 | KEY_B = 5, 38 | KEY_C = 6, 39 | KEY_D = 7, 40 | KEY_E = 8, 41 | KEY_F = 9, 42 | KEY_G = 10, 43 | KEY_H = 11, 44 | KEY_I = 12, 45 | KEY_J = 13, 46 | KEY_K = 14, 47 | KEY_L = 15, 48 | KEY_M = 16, 49 | KEY_N = 17, 50 | KEY_O = 18, 51 | KEY_P = 19, 52 | KEY_Q = 20, 53 | KEY_R = 21, 54 | KEY_S = 22, 55 | KEY_T = 23, 56 | KEY_U = 24, 57 | KEY_V = 25, 58 | KEY_W = 26, 59 | KEY_X = 27, 60 | KEY_Y = 28, 61 | KEY_Z = 29, 62 | 63 | KEY_1 = 30, 64 | KEY_2 = 31, 65 | KEY_3 = 32, 66 | KEY_4 = 33, 67 | KEY_5 = 34, 68 | KEY_6 = 35, 69 | KEY_7 = 36, 70 | KEY_8 = 37, 71 | KEY_9 = 38, 72 | KEY_0 = 39, 73 | 74 | KEY_RETURN = 40, 75 | KEY_ESCAPE = 41, 76 | KEY_BACKSPACE = 42, 77 | KEY_TAB = 43, 78 | KEY_SPACE = 44, 79 | 80 | KEY_MINUS = 45, 81 | KEY_EQUALS = 46, 82 | KEY_LEFTBRACKET = 47, 83 | KEY_RIGHTBRACKET = 48, 84 | KEY_BACKSLASH = 49, 85 | KEY_NONUSHASH = 50, 86 | KEY_SEMICOLON = 51, 87 | KEY_APOSTROPHE = 52, 88 | KEY_GRAVE = 53, 89 | KEY_COMMA = 54, 90 | KEY_PERIOD = 55, 91 | KEY_SLASH = 56, 92 | 93 | KEY_CAPSLOCK = 57, 94 | 95 | KEY_F1 = 58, 96 | KEY_F2 = 59, 97 | KEY_F3 = 60, 98 | KEY_F4 = 61, 99 | KEY_F5 = 62, 100 | KEY_F6 = 63, 101 | KEY_F7 = 64, 102 | KEY_F8 = 65, 103 | KEY_F9 = 66, 104 | KEY_F10 = 67, 105 | KEY_F11 = 68, 106 | KEY_F12 = 69, 107 | 108 | KEY_PRINTSCREEN = 70, 109 | KEY_SCROLLLOCK = 71, 110 | KEY_PAUSE = 72, 111 | KEY_INSERT = 73, 112 | KEY_HOME = 74, 113 | KEY_PAGEUP = 75, 114 | KEY_DELETE = 76, 115 | KEY_END = 77, 116 | KEY_PAGEDOWN = 78, 117 | KEY_RIGHT = 79, 118 | KEY_LEFT = 80, 119 | KEY_DOWN = 81, 120 | KEY_UP = 82, 121 | 122 | KEY_NUMLOCKCLEAR = 83, 123 | KEY_KP_DIVIDE = 84, 124 | KEY_KP_MULTIPLY = 85, 125 | KEY_KP_MINUS = 86, 126 | KEY_KP_PLUS = 87, 127 | KEY_KP_ENTER = 88, 128 | KEY_KP_1 = 89, 129 | KEY_KP_2 = 90, 130 | KEY_KP_3 = 91, 131 | KEY_KP_4 = 92, 132 | KEY_KP_5 = 93, 133 | KEY_KP_6 = 94, 134 | KEY_KP_7 = 95, 135 | KEY_KP_8 = 96, 136 | KEY_KP_9 = 97, 137 | KEY_KP_0 = 98, 138 | KEY_KP_PERIOD = 99, 139 | 140 | KEY_NONUSBACKSLASH = 100, 141 | KEY_APPLICATION = 101, 142 | KEY_POWER = 102, 143 | KEY_KP_EQUALS = 103, 144 | KEY_F13 = 104, 145 | KEY_F14 = 105, 146 | KEY_F15 = 106, 147 | KEY_F16 = 107, 148 | KEY_F17 = 108, 149 | KEY_F18 = 109, 150 | KEY_F19 = 110, 151 | KEY_F20 = 111, 152 | KEY_F21 = 112, 153 | KEY_F22 = 113, 154 | KEY_F23 = 114, 155 | KEY_F24 = 115, 156 | KEY_EXECUTE = 116, 157 | KEY_HELP = 117, 158 | KEY_MENU = 118, 159 | KEY_SELECT = 119, 160 | KEY_STOP = 120, 161 | KEY_AGAIN = 121, 162 | KEY_UNDO = 122, 163 | KEY_CUT = 123, 164 | KEY_COPY = 124, 165 | KEY_PASTE = 125, 166 | KEY_FIND = 126, 167 | KEY_MUTE = 127, 168 | KEY_VOLUMEUP = 128, 169 | KEY_VOLUMEDOWN = 129, 170 | KEY_KP_COMMA = 133, 171 | KEY_KP_EQUALSAS400 = 134, 172 | 173 | KEY_INTERNATIONAL1 = 135, 174 | KEY_INTERNATIONAL2 = 136, 175 | KEY_INTERNATIONAL3 = 137, 176 | KEY_INTERNATIONAL4 = 138, 177 | KEY_INTERNATIONAL5 = 139, 178 | KEY_INTERNATIONAL6 = 140, 179 | KEY_INTERNATIONAL7 = 141, 180 | KEY_INTERNATIONAL8 = 142, 181 | KEY_INTERNATIONAL9 = 143, 182 | KEY_LANG1 = 144, 183 | KEY_LANG2 = 145, 184 | KEY_LANG3 = 146, 185 | KEY_LANG4 = 147, 186 | KEY_LANG5 = 148, 187 | KEY_LANG6 = 149, 188 | KEY_LANG7 = 150, 189 | KEY_LANG8 = 151, 190 | KEY_LANG9 = 152, 191 | 192 | KEY_ALTERASE = 153, 193 | KEY_SYSREQ = 154, 194 | KEY_CANCEL = 155, 195 | KEY_CLEAR = 156, 196 | KEY_PRIOR = 157, 197 | KEY_RETURN2 = 158, 198 | KEY_SEPARATOR = 159, 199 | KEY_OUT = 160, 200 | KEY_OPER = 161, 201 | KEY_CLEARAGAIN = 162, 202 | KEY_CRSEL = 163, 203 | KEY_EXSEL = 164, 204 | 205 | KEY_KP_00 = 176, 206 | KEY_KP_000 = 177, 207 | KEY_THOUSANDSSEPARATOR = 178, 208 | KEY_DECIMALSEPARATOR = 179, 209 | KEY_CURRENCYUNIT = 180, 210 | KEY_CURRENCYSUBUNIT = 181, 211 | KEY_KP_LEFTPAREN = 182, 212 | KEY_KP_RIGHTPAREN = 183, 213 | KEY_KP_LEFTBRACE = 184, 214 | KEY_KP_RIGHTBRACE = 185, 215 | KEY_KP_TAB = 186, 216 | KEY_KP_BACKSPACE = 187, 217 | KEY_KP_A = 188, 218 | KEY_KP_B = 189, 219 | KEY_KP_C = 190, 220 | KEY_KP_D = 191, 221 | KEY_KP_E = 192, 222 | KEY_KP_F = 193, 223 | KEY_KP_XOR = 194, 224 | KEY_KP_POWER = 195, 225 | KEY_KP_PERCENT = 196, 226 | KEY_KP_LESS = 197, 227 | KEY_KP_GREATER = 198, 228 | KEY_KP_AMPERSAND = 199, 229 | KEY_KP_DBLAMPERSAND = 200, 230 | KEY_KP_VERTICALBAR = 201, 231 | KEY_KP_DBLVERTICALBAR = 202, 232 | KEY_KP_COLON = 203, 233 | KEY_KP_HASH = 204, 234 | KEY_KP_SPACE = 205, 235 | KEY_KP_AT = 206, 236 | KEY_KP_EXCLAM = 207, 237 | KEY_KP_MEMSTORE = 208, 238 | KEY_KP_MEMRECALL = 209, 239 | KEY_KP_MEMCLEAR = 210, 240 | KEY_KP_MEMADD = 211, 241 | KEY_KP_MEMSUBTRACT = 212, 242 | KEY_KP_MEMMULTIPLY = 213, 243 | KEY_KP_MEMDIVIDE = 214, 244 | KEY_KP_PLUSMINUS = 215, 245 | KEY_KP_CLEAR = 216, 246 | KEY_KP_CLEARENTRY = 217, 247 | KEY_KP_BINARY = 218, 248 | KEY_KP_OCTAL = 219, 249 | KEY_KP_DECIMAL = 220, 250 | KEY_KP_HEXADECIMAL = 221, 251 | 252 | KEY_LCTRL = 224, 253 | KEY_LSHIFT = 225, 254 | KEY_LALT = 226, 255 | KEY_LGUI = 227, 256 | KEY_RCTRL = 228, 257 | KEY_RSHIFT = 229, 258 | KEY_RALT = 230, 259 | KEY_RGUI = 231, 260 | 261 | KEY_MODE = 257, 262 | 263 | KEY_AUDIONEXT = 258, 264 | KEY_AUDIOPREV = 259, 265 | KEY_AUDIOSTOP = 260, 266 | KEY_AUDIOPLAY = 261, 267 | KEY_AUDIOMUTE = 262, 268 | KEY_MEDIASELECT = 263, 269 | KEY_WWW = 264, 270 | KEY_MAIL = 265, 271 | KEY_CALCULATOR = 266, 272 | KEY_COMPUTER = 267, 273 | KEY_AC_SEARCH = 268, 274 | KEY_AC_HOME = 269, 275 | KEY_AC_BACK = 270, 276 | KEY_AC_FORWARD = 271, 277 | KEY_AC_STOP = 272, 278 | KEY_AC_REFRESH = 273, 279 | KEY_AC_BOOKMARKS = 274, 280 | 281 | KEY_BRIGHTNESSDOWN = 275, 282 | KEY_BRIGHTNESSUP = 276, 283 | KEY_DISPLAYSWITCH = 277, 284 | KEY_KBDILLUMTOGGLE = 278, 285 | KEY_KBDILLUMDOWN = 279, 286 | KEY_KBDILLUMUP = 280, 287 | KEY_EJECT = 281, 288 | KEY_SLEEP = 282, 289 | 290 | KEY_APP1 = 283, 291 | KEY_APP2 = 284, 292 | 293 | NUM_KEYCODES = 512 294 | } 295 | 296 | enum: ubyte 297 | { 298 | MB_LEFT = 1, 299 | MB_MIDDLE = 2, 300 | MB_RIGHT = 3, 301 | MB_X1 = 4, 302 | MB_X2 = 5, 303 | MB_LMASK = SDL_BUTTON!(MB_LEFT), 304 | MB_MMASK = SDL_BUTTON!(MB_MIDDLE), 305 | MB_RMASK = SDL_BUTTON!(MB_RIGHT), 306 | MB_X1MASK = SDL_BUTTON!(MB_X1), 307 | MB_X2MASK = SDL_BUTTON!(MB_X2), 308 | } 309 | -------------------------------------------------------------------------------- /src/dgpu/core/locale.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2025 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | module dgpu.core.locale; 29 | 30 | import std.conv; 31 | import std.format; 32 | import std.process; 33 | 34 | version(Windows) 35 | { 36 | extern(Windows) int GetLocaleInfoW( 37 | const(uint) Locale, 38 | const(uint) LCType, 39 | wchar* lpLCData, 40 | const(int) cchData 41 | ); 42 | 43 | extern(Windows) int GetLocaleInfoA( 44 | const(uint) Locale, 45 | const(uint) LCType, 46 | char* lpLCData, 47 | const(int) cchData 48 | ); 49 | 50 | enum uint LOCALE_USER_DEFAULT = 0x0400; 51 | enum uint LOCALE_SISO639LANGNAME = 0x59; 52 | enum uint LOCALE_SISO3166CTRYNAME = 0x5a; 53 | } 54 | 55 | private string syslocale = "en_US"; 56 | static this() 57 | { 58 | // TODO: don't use GC 59 | version(Windows) 60 | { 61 | string getLanguage() 62 | { 63 | char[16] str; 64 | GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, str.ptr, str.length); 65 | return str.ptr.to!string; 66 | } 67 | 68 | string getCountry() 69 | { 70 | char[16] str; 71 | GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, str.ptr, str.length); 72 | return str.ptr.to!string; 73 | } 74 | 75 | string lang = getLanguage(); 76 | string country = getCountry(); 77 | syslocale = format("%s_%s", lang, country); 78 | } 79 | else version(Posix) 80 | { 81 | string lang = environment.get("LANG", "en_US.utf8"); 82 | string locale, encoding; 83 | formattedRead(lang, "%s.%s", &locale, &encoding); 84 | syslocale = locale; 85 | } 86 | } 87 | 88 | string systemLocale() 89 | { 90 | return syslocale; 91 | } 92 | -------------------------------------------------------------------------------- /src/dgpu/core/logger.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.core.logger; 28 | 29 | import core.stdc.stdlib; 30 | import std.stdio; 31 | import dlib.core.ownership; 32 | 33 | class Logger: Owner 34 | { 35 | bool active = true; 36 | 37 | this(Owner o) 38 | { 39 | super(o); 40 | } 41 | 42 | void log(string msg) 43 | { 44 | if (active) 45 | writeln(msg); 46 | } 47 | 48 | void error(string msg) 49 | { 50 | log(msg); 51 | core.stdc.stdlib.exit(1); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/dgpu/core/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | module dgpu.core; 29 | 30 | public 31 | { 32 | import dgpu.core.application; 33 | import dgpu.core.config; 34 | import dgpu.core.event; 35 | import dgpu.core.gpu; 36 | import dgpu.core.input; 37 | import dgpu.core.keycodes; 38 | import dgpu.core.locale; 39 | import dgpu.core.logger; 40 | import dgpu.core.props; 41 | import dgpu.core.time; 42 | import dgpu.core.vfs; 43 | } 44 | -------------------------------------------------------------------------------- /src/dgpu/core/props.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | 6 | Permission is hereby granted, free of charge, to any person or organization 7 | obtaining a copy of the software and accompanying documentation covered by 8 | this license (the "Software") to use, reproduce, display, distribute, 9 | execute, and transmit the Software, and to prepare derivative works of the 10 | Software, and to permit third-parties to whom the Software is furnished to 11 | do so, all subject to the following: 12 | 13 | The copyright notices in the Software and this entire statement, including 14 | the above license grant, this restriction and the following disclaimer, 15 | must be included in all copies of the Software, in whole or in part, and 16 | all derivative works of the Software, unless such copies or derivative 17 | works are solely in the form of machine-executable object code generated by 18 | a source language processor. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | */ 28 | 29 | module dgpu.core.props; 30 | 31 | import std.stdio; 32 | import std.ascii; 33 | import std.conv; 34 | import dlib.core.memory; 35 | import dlib.core.ownership; 36 | import dlib.container.array; 37 | import dlib.container.dict; 38 | import dlib.text.utils; 39 | import dlib.math.vector; 40 | import dlib.image.color; 41 | import dlib.text.lexer; 42 | 43 | enum DPropType 44 | { 45 | Undefined, 46 | Number, 47 | Vector, 48 | String 49 | } 50 | 51 | struct DProperty 52 | { 53 | DPropType type; 54 | string name; 55 | string data; 56 | 57 | string toString() 58 | { 59 | return data; 60 | } 61 | 62 | double toDouble() 63 | { 64 | return to!double(data); 65 | } 66 | 67 | float toFloat() 68 | { 69 | return to!float(data); 70 | } 71 | 72 | int toInt() 73 | { 74 | return to!int(data); 75 | } 76 | 77 | int toUInt() 78 | { 79 | return to!uint(data); 80 | } 81 | 82 | bool toBool() 83 | { 84 | return cast(bool)cast(int)(to!float(data)); 85 | } 86 | 87 | Vector3f toVector3f() 88 | { 89 | return Vector3f(data); 90 | } 91 | 92 | Vector4f toVector4f() 93 | { 94 | return Vector4f(data); 95 | } 96 | 97 | Color4f toColor4f() 98 | { 99 | return Color4f(Vector4f(data)); 100 | } 101 | } 102 | 103 | class Properties: Owner 104 | { 105 | Dict!(DProperty, string) props; 106 | 107 | this(Owner o) 108 | { 109 | super(o); 110 | props = dict!(DProperty, string); 111 | } 112 | 113 | bool parse(string input) 114 | { 115 | return parseProperties(input, this); 116 | } 117 | 118 | DProperty opIndex(string name) 119 | { 120 | if (name in props) 121 | return props[name]; 122 | else 123 | return DProperty(DPropType.Undefined, ""); 124 | } 125 | 126 | void set(DPropType type, string name, string value) 127 | { 128 | auto p = name in props; 129 | if (p) 130 | { 131 | Delete(p.name); 132 | Delete(p.data); 133 | auto nameCopy = copyStr(name); 134 | auto valueCopy = copyStr(value); 135 | props[nameCopy] = DProperty(type, nameCopy, valueCopy); 136 | } 137 | else 138 | { 139 | auto nameCopy = copyStr(name); 140 | auto valueCopy = copyStr(value); 141 | props[nameCopy] = DProperty(type, nameCopy, valueCopy); 142 | } 143 | } 144 | 145 | DProperty opDispatch(string s)() 146 | { 147 | if (s in props) 148 | return props[s]; 149 | else 150 | return DProperty(DPropType.Undefined, ""); 151 | } 152 | 153 | DProperty* opBinaryRight(string op)(string k) if (op == "in") 154 | { 155 | return (k in props); 156 | } 157 | 158 | void remove(string name) 159 | { 160 | if (name in props) 161 | { 162 | auto n = props[name].name; 163 | Delete(props[name].data); 164 | props.remove(name); 165 | Delete(n); 166 | } 167 | } 168 | 169 | int opApply(int delegate(string, ref DProperty) dg) 170 | { 171 | foreach(k, v; props) 172 | { 173 | dg(k, v); 174 | } 175 | 176 | return 0; 177 | } 178 | 179 | ~this() 180 | { 181 | foreach(k, v; props) 182 | { 183 | Delete(v.data); 184 | Delete(v.name); 185 | } 186 | Delete(props); 187 | } 188 | } 189 | 190 | bool isWhiteStr(string s) 191 | { 192 | bool res; 193 | foreach(c; s) 194 | { 195 | res = false; 196 | foreach(w; std.ascii.whitespace) 197 | { 198 | if (c == w) 199 | res = true; 200 | } 201 | 202 | if (c == '\n' || c == '\r') 203 | res = true; 204 | } 205 | return res; 206 | } 207 | 208 | bool isValidIdentifier(string s) 209 | { 210 | return (isAlpha(s[0]) || s[0] == '_'); 211 | } 212 | 213 | string copyStr(T)(T[] s) 214 | { 215 | auto res = New!(char[])(s.length); 216 | foreach(i, c; s) 217 | res[i] = c; 218 | return cast(string)res; 219 | } 220 | 221 | bool parseProperties(string input, Properties props) 222 | { 223 | enum Expect 224 | { 225 | PropName, 226 | Colon, 227 | Semicolon, 228 | Value, 229 | String, 230 | Vector, 231 | Number 232 | } 233 | 234 | bool res = true; 235 | auto lexer = New!Lexer(input, [":", ";", "\"", "[", "]", ","]); 236 | 237 | lexer.ignoreNewlines = true; 238 | 239 | Expect expect = Expect.PropName; 240 | string propName; 241 | Array!char propValue; 242 | DPropType propType; 243 | 244 | while(true) 245 | { 246 | auto lexeme = lexer.getLexeme(); 247 | if (lexeme.length == 0) 248 | { 249 | if (expect != Expect.PropName) 250 | { 251 | writefln("Error: unexpected end of string"); 252 | res = false; 253 | } 254 | break; 255 | } 256 | 257 | if (isWhiteStr(lexeme) && expect != Expect.String) 258 | continue; 259 | 260 | if (expect == Expect.PropName) 261 | { 262 | if (!isValidIdentifier(lexeme)) 263 | { 264 | writefln("Error: illegal identifier name \"%s\"", lexeme); 265 | res = false; 266 | break; 267 | } 268 | 269 | propName = lexeme; 270 | expect = Expect.Colon; 271 | } 272 | else if (expect == Expect.Colon) 273 | { 274 | if (lexeme != ":") 275 | { 276 | writefln("Error: expected \":\", got \"%s\"", lexeme); 277 | res = false; 278 | break; 279 | } 280 | 281 | expect = Expect.Value; 282 | } 283 | else if (expect == Expect.Semicolon) 284 | { 285 | if (lexeme != ";") 286 | { 287 | writefln("Error: expected \";\", got \"%s\"", lexeme); 288 | res = false; 289 | break; 290 | } 291 | 292 | props.set(propType, propName, cast(string)propValue.data); 293 | 294 | expect = Expect.PropName; 295 | propName = ""; 296 | propValue.free(); 297 | } 298 | else if (expect == Expect.Value) 299 | { 300 | if (lexeme == "\"") 301 | { 302 | propType = DPropType.String; 303 | expect = Expect.String; 304 | } 305 | else if (lexeme == "[") 306 | { 307 | propType = DPropType.Vector; 308 | expect = Expect.Vector; 309 | propValue.append(lexeme); 310 | } 311 | else 312 | { 313 | propType = DPropType.Number; 314 | propValue.append(lexeme); 315 | expect = Expect.Semicolon; 316 | } 317 | } 318 | else if (expect == Expect.String) 319 | { 320 | if (lexeme == "\"") 321 | expect = Expect.Semicolon; 322 | else 323 | propValue.append(lexeme); 324 | } 325 | else if (expect == Expect.Vector) 326 | { 327 | if (lexeme == "]") 328 | expect = Expect.Semicolon; 329 | 330 | propValue.append(lexeme); 331 | } 332 | } 333 | 334 | propValue.free(); 335 | Delete(lexer); 336 | 337 | return res; 338 | } 339 | -------------------------------------------------------------------------------- /src/dgpu/core/resource.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | 6 | Permission is hereby granted, free of charge, to any person or organization 7 | obtaining a copy of the software and accompanying documentation covered by 8 | this license (the "Software") to use, reproduce, display, distribute, 9 | execute, and transmit the Software, and to prepare derivative works of the 10 | Software, and to permit third-parties to whom the Software is furnished to 11 | do so, all subject to the following: 12 | 13 | The copyright notices in the Software and this entire statement, including 14 | the above license grant, this restriction and the following disclaimer, 15 | must be included in all copies of the Software, in whole or in part, and 16 | all derivative works of the Software, unless such copies or derivative 17 | works are solely in the form of machine-executable object code generated by 18 | a source language processor. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | */ 28 | module dgpu.core.resource; 29 | 30 | import bindbc.wgpu; 31 | 32 | interface Resource 33 | { 34 | void upload(); 35 | WGPUBindGroup bindGroup() @property; 36 | } 37 | -------------------------------------------------------------------------------- /src/dgpu/core/time.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | module dgpu.core.time; 29 | 30 | import dlib.core.ownership; 31 | 32 | struct Time 33 | { 34 | double delta; 35 | double elapsed; 36 | } 37 | 38 | class Cadencer: Owner 39 | { 40 | protected: 41 | double elapsedTime = 0.0; 42 | double timeStep = 0.0; 43 | double fpsTimeCounter = 0.0; 44 | int fpsCounter = 0; 45 | void delegate(Time) callback; 46 | 47 | public: 48 | int fps = 0; 49 | 50 | this(void delegate(Time) callback, uint freq, Owner owner) 51 | { 52 | super(owner); 53 | this.callback = callback; 54 | timeStep = 1.0 / cast(double)freq; 55 | } 56 | 57 | void update(Time t) 58 | { 59 | elapsedTime += t.delta; 60 | fpsTimeCounter += t.delta; 61 | 62 | if (elapsedTime >= timeStep) 63 | { 64 | callback(Time(timeStep, t.elapsed)); 65 | elapsedTime -= timeStep; 66 | fpsCounter++; 67 | } 68 | 69 | if (fpsTimeCounter >= 1.0) // 1 sec interval 70 | { 71 | fps = fpsCounter; 72 | fpsCounter = 0; 73 | fpsTimeCounter = 0; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/dgpu/core/vfs.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | module dgpu.core.vfs; 29 | 30 | import std.string; 31 | import std.path; 32 | import dlib.core.memory; 33 | import dlib.core.stream; 34 | import dlib.container.array; 35 | import dlib.container.dict; 36 | import dlib.filesystem.filesystem; 37 | import dlib.filesystem.stdfs; 38 | 39 | class StdDirFileSystem: ReadOnlyFileSystem 40 | { 41 | StdFileSystem stdfs; 42 | string rootDir; 43 | 44 | this(string rootDir) 45 | { 46 | this.rootDir = rootDir; 47 | stdfs = New!StdFileSystem(); 48 | } 49 | 50 | bool stat(string filename, out FileStat stat) 51 | { 52 | string path = format("%s/%s", rootDir, filename); 53 | return stdfs.stat(path, stat); 54 | } 55 | 56 | InputStream openForInput(string filename) 57 | { 58 | string path = format("%s/%s", rootDir, filename); 59 | return stdfs.openForInput(path); 60 | } 61 | 62 | Directory openDir(string dir) 63 | { 64 | string path = format("%s/%s", rootDir, dir); 65 | return stdfs.openDir(path); 66 | } 67 | 68 | ~this() 69 | { 70 | Delete(stdfs); 71 | } 72 | } 73 | 74 | class VirtualFileSystem: ReadOnlyFileSystem 75 | { 76 | Array!ReadOnlyFileSystem mounted; 77 | 78 | this() 79 | { 80 | } 81 | 82 | void mount(string dir) 83 | { 84 | StdDirFileSystem fs = New!StdDirFileSystem(dir); 85 | mounted.append(fs); 86 | } 87 | 88 | void mount(ReadOnlyFileSystem fs) 89 | { 90 | mounted.append(fs); 91 | } 92 | 93 | string containingDir(string filename) 94 | { 95 | string res; 96 | foreach(i, fs; mounted) 97 | { 98 | if (cast(StdDirFileSystem)fs) 99 | { 100 | FileStat s; 101 | if (fs.stat(filename, s)) 102 | { 103 | res = (cast(StdDirFileSystem)fs).rootDir; 104 | break; 105 | } 106 | } 107 | } 108 | return res; 109 | } 110 | 111 | bool stat(string filename, out FileStat stat) 112 | { 113 | bool res = false; 114 | foreach(i, fs; mounted) 115 | { 116 | FileStat s; 117 | if (fs.stat(filename, s)) 118 | { 119 | stat = s; 120 | res = true; 121 | break; 122 | } 123 | } 124 | 125 | return res; 126 | } 127 | 128 | InputStream openForInput(string filename) 129 | { 130 | foreach(i, fs; mounted) 131 | { 132 | FileStat s; 133 | if (fs.stat(filename, s)) 134 | { 135 | return fs.openForInput(filename); 136 | } 137 | } 138 | 139 | return null; 140 | } 141 | 142 | Directory openDir(string path) 143 | { 144 | // TODO 145 | return null; 146 | } 147 | 148 | ~this() 149 | { 150 | foreach(i, fs; mounted) 151 | Delete(fs); 152 | mounted.free(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/dgpu/render/drawable.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.render.drawable; 28 | 29 | import dlib.core.memory; 30 | import dlib.core.ownership; 31 | import bindbc.wgpu; 32 | import dgpu.asset.geometry; 33 | import dgpu.asset.entity; 34 | 35 | class Drawable: Geometry 36 | { 37 | this(Owner o) 38 | { 39 | super(o); 40 | } 41 | 42 | void draw(Entity entity, WGPURenderPassEncoder encoder) 43 | { 44 | // override me 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/dgpu/render/pass.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2025 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.render.pass; 28 | 29 | import std.conv; 30 | import std.file; 31 | import bindbc.wgpu; 32 | import dlib.core.memory; 33 | import dlib.core.ownership; 34 | import dlib.image.color; 35 | import dgpu.core.gpu; 36 | import dgpu.core.time; 37 | import dgpu.render.renderer; 38 | import dgpu.render.target; 39 | import dgpu.render.resource; 40 | import dgpu.render.pipeline; 41 | import dgpu.render.shader; 42 | import dgpu.render.drawable; 43 | import dgpu.asset.scene; 44 | 45 | class RenderPass: Owner 46 | { 47 | string label; 48 | Renderer renderer; 49 | Color4f clearColor = Color4f(0.7f, 0.7f, 0.7f, 1.0f); 50 | float clearDepth = 1.0f; 51 | uint clearStencil = 0; 52 | 53 | Shader simpleShader; 54 | RenderPipeline simplePipeline; 55 | 56 | PassResource resource; 57 | 58 | this(Renderer renderer) 59 | { 60 | super(renderer); 61 | label = toHash.to!string; 62 | this.renderer = renderer; 63 | 64 | resource = New!PassResource(renderer, this); 65 | 66 | //simpleShader = New!Shader(readText("data/shaders/pbr.wgsl"), "vs_main", "fs_main", renderer, this); 67 | //simpleShader = New!Shader(cast(uint[])read("data/shaders/pbr.spv"), "vs_main", "fs_main", renderer, this); 68 | 69 | simpleShader = New!Shader( 70 | cast(uint[])read("data/shaders/pbr.vert.spv"), "main", 71 | cast(uint[])read("data/shaders/pbr.frag.spv"), "main", 72 | renderer, this); 73 | 74 | simplePipeline = New!RenderPipeline(this, simpleShader, renderer.screenRenderTarget, renderer, this); 75 | 76 | if (renderer.screenRenderTarget.colorFormat == WGPUTextureFormat.BGRA8UnormSrgb) 77 | { 78 | clearColor = clearColor.toLinear(); 79 | } 80 | } 81 | 82 | WGPURenderPassEncoder begin(WGPUTextureView colorTargetView, WGPUTextureView depthStencilTargetView, WGPUCommandEncoder encoder) 83 | { 84 | Color4f cColor = clearColor; 85 | if (renderer.screenRenderTarget.colorFormat == WGPUTextureFormat.BGRA8UnormSrgb) 86 | { 87 | cColor = cColor.toLinear(); 88 | } 89 | 90 | WGPURenderPassColorAttachment colorAttachment = { 91 | view: colorTargetView, 92 | depthSlice: WGPU_DEPTH_SLICE_UNDEFINED, 93 | resolveTarget: null, 94 | loadOp: WGPULoadOp.Clear, 95 | storeOp: WGPUStoreOp.Store, 96 | clearValue: WGPUColor(cColor.r, cColor.g, cColor.b, cColor.a) 97 | }; 98 | WGPURenderPassDepthStencilAttachment depthStencilAttachmentDescriptor = { 99 | view: depthStencilTargetView, 100 | depthLoadOp: WGPULoadOp.Clear, 101 | depthStoreOp: WGPUStoreOp.Store, 102 | depthClearValue: clearDepth, 103 | depthReadOnly: false, 104 | stencilLoadOp: WGPULoadOp.Clear, 105 | stencilStoreOp: WGPUStoreOp.Store, 106 | stencilClearValue: clearStencil, 107 | stencilReadOnly: false 108 | }; 109 | WGPURenderPassDescriptor renderPassDescriptor = { 110 | colorAttachments: &colorAttachment, 111 | colorAttachmentCount: 1, 112 | depthStencilAttachment: &depthStencilAttachmentDescriptor 113 | }; 114 | WGPURenderPassEncoder renderPass = wgpuCommandEncoderBeginRenderPass(encoder, &renderPassDescriptor); 115 | 116 | return renderPass; 117 | } 118 | 119 | void renderScene(Scene scene, WGPURenderPassEncoder encoder) 120 | { 121 | resource.upload(); 122 | wgpuRenderPassEncoderSetBindGroup(encoder, ResourceGroupIndex.PerPass, resource.bindGroup, 0, null); 123 | 124 | foreach(material; scene.materials) 125 | { 126 | MaterialResource materialResource; 127 | if (material in renderer.materialResource) 128 | materialResource = renderer.materialResource[material]; 129 | else { 130 | materialResource = New!MaterialResource(renderer, material, material); 131 | renderer.materialResource[material] = materialResource; 132 | } 133 | 134 | auto materialParams = &materialResource.uniforms; 135 | materialParams.baseColorFactor = material.baseColorFactor; 136 | materialParams.roughnessMetallicFactor = material.roughnessMetallicFactor; 137 | materialResource.upload(); 138 | 139 | wgpuRenderPassEncoderSetBindGroup(encoder, ResourceGroupIndex.PerMaterial, materialResource.bindGroup, 0, null); 140 | 141 | wgpuRenderPassEncoderSetPipeline(encoder, simplePipeline.pipeline); 142 | 143 | foreach(entity; scene.entitiesByMaterial(material)) 144 | { 145 | if (entity.geometry) 146 | { 147 | auto uni = &renderer.geometryResource.uniforms; 148 | uni.modelViewMatrix = renderer.rendererResource.uniforms.viewMatrix * entity.geometry.modelMatrix; 149 | uni.normalMatrix = uni.modelViewMatrix.inverse.transposed; 150 | renderer.geometryResource.upload(); 151 | 152 | wgpuRenderPassEncoderSetBindGroup(encoder, ResourceGroupIndex.PerEntity, renderer.geometryResource.bindGroup, 0, null); 153 | 154 | Drawable drawable = cast(Drawable)entity.geometry; 155 | if (drawable) 156 | drawable.draw(entity, encoder); 157 | } 158 | } 159 | } 160 | } 161 | 162 | void end(WGPURenderPassEncoder encoder) 163 | { 164 | wgpuRenderPassEncoderEnd(encoder); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/dgpu/render/pipeline.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2025 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.render.pipeline; 28 | 29 | import std.string; 30 | import std.conv; 31 | import bindbc.wgpu; 32 | import dlib.core.memory; 33 | import dlib.core.ownership; 34 | import dgpu.core.gpu; 35 | import dgpu.render.renderer; 36 | import dgpu.render.target; 37 | import dgpu.render.shader; 38 | import dgpu.render.pass; 39 | 40 | class RenderPipeline: Owner 41 | { 42 | Renderer renderer; 43 | RenderPass pass; 44 | Shader shader; 45 | RenderTarget renderTarget; 46 | string label; 47 | WGPURenderPipeline pipeline; 48 | 49 | this(RenderPass pass, Shader shader, RenderTarget renderTarget, Renderer renderer, Owner owner) 50 | { 51 | super(owner); 52 | label = toHash.to!string; 53 | 54 | this.pass = pass; 55 | this.shader = shader; 56 | this.renderTarget = renderTarget; 57 | this.renderer = renderer; 58 | 59 | init(); 60 | } 61 | 62 | void init() 63 | { 64 | WGPUBindGroupLayout[4] layouts = [ 65 | renderer.rendererResourceLayout, 66 | renderer.passResourceLayout, 67 | renderer.materialResourceLayout, 68 | renderer.geometryResourceLayout 69 | ]; 70 | 71 | WGPUPipelineLayoutDescriptor pipelineLayoutDescriptor = { 72 | bindGroupLayouts: layouts.ptr, 73 | bindGroupLayoutCount: layouts.length 74 | }; 75 | WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(renderer.gpu.device, &pipelineLayoutDescriptor); 76 | 77 | WGPUBlendState blendState = { 78 | color: { 79 | srcFactor: WGPUBlendFactor.One, 80 | dstFactor: WGPUBlendFactor.Zero, 81 | operation: WGPUBlendOperation.Add 82 | }, 83 | alpha: { 84 | srcFactor: WGPUBlendFactor.One, 85 | dstFactor: WGPUBlendFactor.Zero, 86 | operation: WGPUBlendOperation.Add 87 | } 88 | }; 89 | 90 | WGPUColorTargetState colorTargetState = { 91 | format: renderTarget.colorFormat, 92 | blend: &blendState, 93 | writeMask: WGPUColorWriteMask.All 94 | }; 95 | 96 | size_t vertexSize = float.sizeof * 3; 97 | size_t texcoordSize = float.sizeof * 2; 98 | size_t normalSize = float.sizeof * 3; 99 | 100 | WGPUVertexAttribute[3] attributes = 101 | [ 102 | WGPUVertexAttribute(WGPUVertexFormat.Float32x3, 0, 0), //position 103 | WGPUVertexAttribute(WGPUVertexFormat.Float32x2, vertexSize, 1), //texcoord 104 | WGPUVertexAttribute(WGPUVertexFormat.Float32x3, vertexSize + texcoordSize, 2) //normal 105 | ]; 106 | 107 | WGPUVertexBufferLayout vertexBufferLayout = { 108 | arrayStride: vertexSize + texcoordSize + normalSize, 109 | stepMode: WGPUVertexStepMode.Vertex, 110 | attributeCount: attributes.length, 111 | attributes: attributes.ptr 112 | }; 113 | 114 | WGPUVertexState vertexState = { 115 | module_: shader.modules.vertex, 116 | entryPoint: shader.vertexEntryPoint.toStringz, 117 | bufferCount: 1, 118 | buffers: &vertexBufferLayout 119 | }; 120 | 121 | WGPUFragmentState fragmentState = { 122 | module_: shader.modules.fragment, 123 | entryPoint: shader.fragmentEntryPoint.toStringz, 124 | targetCount: 1, 125 | targets: &colorTargetState 126 | }; 127 | 128 | WGPUStencilFaceState stencilStateFront = { 129 | compare: WGPUCompareFunction.Always, 130 | failOp: WGPUStencilOperation.Keep, 131 | depthFailOp: WGPUStencilOperation.Keep, 132 | passOp: WGPUStencilOperation.Keep 133 | }; 134 | 135 | WGPUStencilFaceState stencilStateBack = { 136 | compare: WGPUCompareFunction.Always, 137 | failOp: WGPUStencilOperation.Keep, 138 | depthFailOp: WGPUStencilOperation.Keep, 139 | passOp: WGPUStencilOperation.Keep 140 | }; 141 | 142 | WGPUDepthStencilState depthStencilState = { 143 | nextInChain: null, 144 | format: renderTarget.depthStencilFormat, 145 | depthWriteEnabled: true, 146 | depthCompare: WGPUCompareFunction.Less, 147 | stencilFront: stencilStateFront, 148 | stencilBack: stencilStateBack, 149 | stencilReadMask: 0, 150 | stencilWriteMask: 0, 151 | depthBias: 0, 152 | depthBiasSlopeScale: 1, 153 | depthBiasClamp: 1 154 | }; 155 | 156 | WGPURenderPipelineDescriptor renderPipelineDescriptor = { 157 | label: label.toStringz, 158 | layout: pipelineLayout, 159 | vertex: vertexState, 160 | primitive: { 161 | topology: WGPUPrimitiveTopology.TriangleList, 162 | stripIndexFormat: WGPUIndexFormat.Undefined, 163 | frontFace: WGPUFrontFace.CCW, 164 | cullMode: WGPUCullMode.None 165 | }, 166 | multisample: { 167 | count: 1, 168 | mask: ~0, 169 | alphaToCoverageEnabled: false 170 | }, 171 | fragment: &fragmentState, 172 | depthStencil: &depthStencilState 173 | }; 174 | 175 | pipeline = wgpuDeviceCreateRenderPipeline(renderer.gpu.device, &renderPipelineDescriptor); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/dgpu/render/renderer.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.render.renderer; 28 | 29 | import std.conv; 30 | import std.string; 31 | import bindbc.wgpu; 32 | import dlib.core.memory; 33 | import dlib.core.ownership; 34 | import dlib.container.array; 35 | import dlib.container.dict; 36 | import dlib.math.vector; 37 | import dlib.math.matrix; 38 | import dlib.math.transformation; 39 | import dgpu.core.application; 40 | import dgpu.core.event; 41 | import dgpu.core.time; 42 | import dgpu.core.gpu; 43 | import dgpu.asset.scene; 44 | import dgpu.asset.material; 45 | import dgpu.render.target; 46 | import dgpu.render.pass; 47 | import dgpu.render.resource; 48 | 49 | WGPUBindGroupLayout createRendererResourceLayout(Renderer renderer) 50 | { 51 | WGPUBindGroupLayoutEntry[1] entries = [ 52 | { 53 | nextInChain: null, 54 | binding: 0, 55 | visibility: WGPUShaderStage.Vertex | WGPUShaderStage.Fragment, 56 | buffer: { 57 | nextInChain: null, 58 | type: WGPUBufferBindingType.Uniform, 59 | hasDynamicOffset: false, 60 | minBindingSize: RendererUniforms.sizeof 61 | } 62 | } 63 | ]; 64 | 65 | WGPUBindGroupLayoutDescriptor bindGroupLayoutDescriptor = { 66 | label: renderer.label.toStringz, 67 | entries: entries.ptr, 68 | entryCount: entries.length 69 | }; 70 | 71 | return wgpuDeviceCreateBindGroupLayout(renderer.gpu.device, &bindGroupLayoutDescriptor); 72 | } 73 | 74 | WGPUBindGroupLayout createMaterialResourceLayout(Renderer renderer) 75 | { 76 | WGPUBindGroupLayoutEntry[7] entries = [ 77 | { 78 | nextInChain: null, 79 | binding: 0, 80 | visibility: WGPUShaderStage.Fragment, 81 | buffer: { 82 | nextInChain: null, 83 | type: WGPUBufferBindingType.Uniform, 84 | hasDynamicOffset: false, 85 | minBindingSize: MaterialUniforms.sizeof 86 | } 87 | }, 88 | { 89 | nextInChain: null, 90 | binding: 1, 91 | visibility: WGPUShaderStage.Fragment, 92 | sampler: { 93 | nextInChain: null, 94 | type: WGPUSamplerBindingType.Filtering 95 | } 96 | }, 97 | { 98 | nextInChain: null, 99 | binding: 2, 100 | visibility: WGPUShaderStage.Fragment, 101 | texture: { 102 | nextInChain: null, 103 | sampleType: WGPUTextureSampleType.Float, 104 | viewDimension: WGPUTextureViewDimension.D2Array, 105 | multisampled: false 106 | } 107 | }, 108 | { 109 | nextInChain: null, 110 | binding: 3, 111 | visibility: WGPUShaderStage.Fragment, 112 | sampler: { 113 | nextInChain: null, 114 | type: WGPUSamplerBindingType.Filtering 115 | } 116 | }, 117 | { 118 | nextInChain: null, 119 | binding: 4, 120 | visibility: WGPUShaderStage.Fragment, 121 | texture: { 122 | nextInChain: null, 123 | sampleType: WGPUTextureSampleType.Float, 124 | viewDimension: WGPUTextureViewDimension.D2Array, 125 | multisampled: false 126 | } 127 | }, 128 | { 129 | nextInChain: null, 130 | binding: 5, 131 | visibility: WGPUShaderStage.Fragment, 132 | sampler: { 133 | nextInChain: null, 134 | type: WGPUSamplerBindingType.Filtering 135 | } 136 | }, 137 | { 138 | nextInChain: null, 139 | binding: 6, 140 | visibility: WGPUShaderStage.Fragment, 141 | texture: { 142 | nextInChain: null, 143 | sampleType: WGPUTextureSampleType.Float, 144 | viewDimension: WGPUTextureViewDimension.D2Array, 145 | multisampled: false 146 | } 147 | } 148 | ]; 149 | 150 | WGPUBindGroupLayoutDescriptor bindGroupLayoutDescriptor = { 151 | label: renderer.label.toStringz, 152 | entries: entries.ptr, 153 | entryCount: entries.length 154 | }; 155 | 156 | return wgpuDeviceCreateBindGroupLayout(renderer.gpu.device, &bindGroupLayoutDescriptor); 157 | } 158 | 159 | WGPUBindGroupLayout createPassResourceLayout(Renderer renderer) 160 | { 161 | WGPUBindGroupLayoutEntry[1] entries = [ 162 | { 163 | nextInChain: null, 164 | binding: 0, 165 | visibility: WGPUShaderStage.Vertex | WGPUShaderStage.Fragment, 166 | buffer: { 167 | nextInChain: null, 168 | type: WGPUBufferBindingType.Uniform, 169 | hasDynamicOffset: false, 170 | minBindingSize: PassUniforms.sizeof 171 | } 172 | } 173 | ]; 174 | 175 | WGPUBindGroupLayoutDescriptor bindGroupLayoutDescriptor = { 176 | label: renderer.label.toStringz, 177 | entries: entries.ptr, 178 | entryCount: entries.length 179 | }; 180 | 181 | return wgpuDeviceCreateBindGroupLayout(renderer.gpu.device, &bindGroupLayoutDescriptor); 182 | } 183 | 184 | WGPUBindGroupLayout createGeometryResourceLayout(Renderer renderer) 185 | { 186 | WGPUBindGroupLayoutEntry[1] entries = [ 187 | { 188 | nextInChain: null, 189 | binding: 0, 190 | visibility: WGPUShaderStage.Vertex | WGPUShaderStage.Fragment, 191 | buffer: { 192 | nextInChain: null, 193 | type: WGPUBufferBindingType.Uniform, 194 | hasDynamicOffset: false, 195 | minBindingSize: GeometryUniforms.sizeof 196 | } 197 | } 198 | ]; 199 | 200 | WGPUBindGroupLayoutDescriptor bindGroupLayoutDescriptor = { 201 | label: renderer.label.toStringz, 202 | entries: entries.ptr, 203 | entryCount: entries.length 204 | }; 205 | 206 | return wgpuDeviceCreateBindGroupLayout(renderer.gpu.device, &bindGroupLayoutDescriptor); 207 | } 208 | 209 | class Renderer: EventListener 210 | { 211 | string label; 212 | GPU gpu; 213 | ScreenRenderTarget screenRenderTarget; 214 | Array!RenderPass passes; 215 | WGPUBindGroupLayout rendererResourceLayout; 216 | WGPUBindGroupLayout passResourceLayout; 217 | WGPUBindGroupLayout materialResourceLayout; 218 | WGPUBindGroupLayout geometryResourceLayout; 219 | RendererResource rendererResource; 220 | Dict!(MaterialResource, Material) materialResource; 221 | GeometryResource geometryResource; 222 | 223 | this(Application app, Owner owner) 224 | { 225 | super(app.eventManager, owner); 226 | label = toHash.to!string; 227 | gpu = app.gpu; 228 | screenRenderTarget = New!ScreenRenderTarget(gpu, app.width, app.height, this); 229 | app.logger.log("Surface format: " ~ to!string(screenRenderTarget.surfaceFormat)); 230 | 231 | rendererResourceLayout = createRendererResourceLayout(this); 232 | passResourceLayout = createPassResourceLayout(this); 233 | materialResourceLayout = createMaterialResourceLayout(this); 234 | geometryResourceLayout = createGeometryResourceLayout(this); 235 | 236 | rendererResource = New!RendererResource(this, this); 237 | materialResource = New!(Dict!(MaterialResource, Material))(); 238 | geometryResource = New!GeometryResource(this, this); 239 | 240 | createPass(); 241 | } 242 | 243 | ~this() 244 | { 245 | passes.free(); 246 | Delete(materialResource); 247 | } 248 | 249 | RenderPass createPass() 250 | { 251 | RenderPass pass = New!RenderPass(this); 252 | passes.append(pass); 253 | return pass; 254 | } 255 | 256 | override void onResize(uint width, uint height) 257 | { 258 | screenRenderTarget.resize(width, height); 259 | } 260 | 261 | void update(Time t) 262 | { 263 | processEvents(); 264 | } 265 | 266 | void renderScene(Scene scene) 267 | { 268 | WGPUCommandEncoder commandEncoder = gpu.createCommandEncoder(); 269 | 270 | float width = cast(float)screenRenderTarget.width; 271 | float height = cast(float)screenRenderTarget.height; 272 | float aspect = screenRenderTarget.aspectRatio; 273 | 274 | if (scene.activeCamera) 275 | { 276 | rendererResource.uniforms.viewMatrix = scene.activeCamera.viewMatrix; 277 | rendererResource.uniforms.invViewMatrix = scene.activeCamera.invViewMatrix; 278 | rendererResource.uniforms.projectionMatrix = scene.activeCamera.projectionMatrix(aspect); 279 | } 280 | else 281 | { 282 | rendererResource.uniforms.viewMatrix = translationMatrix(vec3(0, 0, 0)); 283 | rendererResource.uniforms.invViewMatrix = rendererResource.uniforms.viewMatrix.inverse; 284 | rendererResource.uniforms.projectionMatrix = perspectiveMatrix(60.0f, aspect, 0.01f, 1000.0f); 285 | } 286 | rendererResource.uniforms.view = vec4(width, height, aspect, 0.0f); 287 | rendererResource.upload(); 288 | 289 | RenderBuffer colorBuffer = screenRenderTarget.nextBackBuffer(); 290 | if (!colorBuffer.view) 291 | return; 292 | RenderBuffer depthStencilBuffer = screenRenderTarget.depthStencilBuffer(); 293 | if (!depthStencilBuffer.view) 294 | return; 295 | 296 | bool canSubmit = true; 297 | foreach(pass; passes) 298 | { 299 | WGPURenderPassEncoder encoder = pass.begin(colorBuffer.view, depthStencilBuffer.view, commandEncoder); 300 | 301 | if (encoder) 302 | { 303 | wgpuRenderPassEncoderSetViewport(encoder, 0.0f, 0.0f, width, height, 0.0f, 1.0f); 304 | wgpuRenderPassEncoderSetScissorRect(encoder, 0, 0, screenRenderTarget.width, screenRenderTarget.height); 305 | wgpuRenderPassEncoderSetBindGroup(encoder, ResourceGroupIndex.PerFrame, rendererResource.bindGroup, 0, null); 306 | pass.renderScene(scene, encoder); 307 | pass.end(encoder); 308 | wgpuRenderPassEncoderRelease(encoder); 309 | } 310 | else 311 | { 312 | canSubmit = false; 313 | break; 314 | } 315 | } 316 | 317 | if (!canSubmit) return; 318 | 319 | gpu.submitCommands(commandEncoder); 320 | screenRenderTarget.present(); 321 | 322 | wgpuCommandEncoderRelease(commandEncoder); 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/dgpu/render/resource.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.render.resource; 28 | 29 | import core.stdc.string: memcpy; 30 | import std.conv; 31 | import std.string; 32 | import bindbc.wgpu; 33 | import dlib.core.ownership; 34 | import dlib.math.vector; 35 | import dlib.math.matrix; 36 | import dgpu.core.gpu; 37 | import dgpu.core.resource; 38 | import dgpu.asset.material; 39 | import dgpu.render.renderer; 40 | 41 | enum ResourceGroupIndex: uint 42 | { 43 | PerFrame = 0, 44 | PerPass = 1, 45 | PerMaterial = 2, 46 | PerEntity = 3 47 | } 48 | 49 | struct RendererUniforms 50 | { 51 | mat4 viewMatrix; 52 | mat4 invViewMatrix; 53 | mat4 projectionMatrix; 54 | vec4 view; // width, height, aspectRatio, 0 55 | } 56 | 57 | class RendererResource: Owner, Resource 58 | { 59 | string label; 60 | Renderer renderer; 61 | RendererUniforms uniforms; 62 | WGPUBuffer uniformBuffer; 63 | WGPUBindGroup _bindGroup; 64 | 65 | this(Renderer renderer, Owner owner) 66 | { 67 | super(owner); 68 | label = "RendererResourceUB_" ~ toHash.to!string; 69 | this.renderer = renderer; 70 | 71 | uniforms.viewMatrix = mat4.identity; 72 | uniforms.invViewMatrix = mat4.identity; 73 | uniforms.projectionMatrix = mat4.identity; 74 | uniforms.view = vec4(1.0f, 1.0f, 1.0f, 0.0f); 75 | 76 | WGPUBufferDescriptor uniformBufferDescriptor = { 77 | nextInChain: null, 78 | label: label.toStringz, 79 | usage: WGPUBufferUsage.Uniform | WGPUBufferUsage.CopyDst, 80 | size: RendererUniforms.sizeof, 81 | mappedAtCreation: true 82 | }; 83 | uniformBuffer = wgpuDeviceCreateBuffer(renderer.gpu.device, &uniformBufferDescriptor); 84 | void* destinationPtr = wgpuBufferGetMappedRange(uniformBuffer, 0, RendererUniforms.sizeof); 85 | memcpy(destinationPtr, cast(ubyte*)&uniforms, RendererUniforms.sizeof); 86 | wgpuBufferUnmap(uniformBuffer); 87 | 88 | WGPUBindGroupEntry entry = { 89 | nextInChain: null, 90 | binding: 0, 91 | buffer: uniformBuffer, 92 | offset: 0, 93 | size: RendererUniforms.sizeof, 94 | sampler: null, 95 | textureView: null 96 | }; 97 | WGPUBindGroupDescriptor bindGroupDescriptor = { 98 | label: label.toStringz, 99 | layout: renderer.rendererResourceLayout, 100 | entries: &entry, 101 | entryCount: 1 102 | }; 103 | _bindGroup = wgpuDeviceCreateBindGroup(renderer.gpu.device, &bindGroupDescriptor); 104 | } 105 | 106 | void upload() 107 | { 108 | wgpuQueueWriteBuffer(renderer.gpu.queue, uniformBuffer, 0, cast(ubyte*)&uniforms, RendererUniforms.sizeof); 109 | } 110 | 111 | WGPUBindGroup bindGroup() @property 112 | { 113 | return _bindGroup; 114 | } 115 | } 116 | 117 | struct PassUniforms 118 | { 119 | vec4 placeholder; 120 | } 121 | 122 | class PassResource: Owner, Resource 123 | { 124 | string label; 125 | Renderer renderer; 126 | PassUniforms uniforms; 127 | WGPUBuffer uniformBuffer; 128 | WGPUBindGroup _bindGroup; 129 | 130 | this(Renderer renderer, Owner owner) 131 | { 132 | super(owner); 133 | label = "PassResourceUB_" ~ toHash.to!string; 134 | this.renderer = renderer; 135 | 136 | uniforms.placeholder = vec4(0, 0, 0, 0); 137 | 138 | WGPUBufferDescriptor uniformBufferDescriptor = { 139 | nextInChain: null, 140 | label: label.toStringz, 141 | usage: WGPUBufferUsage.Uniform | WGPUBufferUsage.CopyDst, 142 | size: PassUniforms.sizeof, 143 | mappedAtCreation: true 144 | }; 145 | uniformBuffer = wgpuDeviceCreateBuffer(renderer.gpu.device, &uniformBufferDescriptor); 146 | void* destinationPtr = wgpuBufferGetMappedRange(uniformBuffer, 0, PassUniforms.sizeof); 147 | memcpy(destinationPtr, cast(ubyte*)&uniforms, PassUniforms.sizeof); 148 | wgpuBufferUnmap(uniformBuffer); 149 | 150 | WGPUBindGroupEntry entry = { 151 | binding: 0, 152 | buffer: uniformBuffer, 153 | offset: 0, 154 | size: PassUniforms.sizeof 155 | }; 156 | 157 | WGPUBindGroupDescriptor bindGroupDescriptor = { 158 | label: label.toStringz, 159 | layout: renderer.passResourceLayout, 160 | entries: &entry, 161 | entryCount: 1 162 | }; 163 | _bindGroup = wgpuDeviceCreateBindGroup(renderer.gpu.device, &bindGroupDescriptor); 164 | } 165 | 166 | void upload() 167 | { 168 | wgpuQueueWriteBuffer(renderer.gpu.queue, uniformBuffer, 0, cast(ubyte*)&uniforms, PassUniforms.sizeof); 169 | } 170 | 171 | WGPUBindGroup bindGroup() @property 172 | { 173 | return _bindGroup; 174 | } 175 | } 176 | 177 | struct MaterialUniforms 178 | { 179 | vec4 baseColorFactor; 180 | vec4 roughnessMetallicFactor; 181 | } 182 | 183 | class MaterialResource: Owner, Resource 184 | { 185 | string label; 186 | Renderer renderer; 187 | MaterialUniforms uniforms; 188 | Material material; 189 | WGPUBuffer uniformBuffer; 190 | WGPUBindGroup _bindGroup; 191 | 192 | this(Renderer renderer, Material material, Owner o) 193 | { 194 | super(o); 195 | label = "MaterialResourceUB_" ~ toHash.to!string; 196 | this.renderer = renderer; 197 | this.material = material; 198 | 199 | WGPUBufferDescriptor uniformBufferDescriptor = { 200 | nextInChain: null, 201 | label: label.toStringz, 202 | usage: WGPUBufferUsage.Uniform | WGPUBufferUsage.CopyDst, 203 | size: MaterialUniforms.sizeof, 204 | mappedAtCreation: true 205 | }; 206 | uniformBuffer = wgpuDeviceCreateBuffer(renderer.gpu.device, &uniformBufferDescriptor); 207 | void* destinationPtr = wgpuBufferGetMappedRange(uniformBuffer, 0, MaterialUniforms.sizeof); 208 | memcpy(destinationPtr, cast(ubyte*)&uniforms, MaterialUniforms.sizeof); 209 | wgpuBufferUnmap(uniformBuffer); 210 | 211 | WGPUBindGroupEntry[7] entries = [ 212 | { 213 | binding: 0, 214 | buffer: uniformBuffer, 215 | offset: 0, 216 | size: MaterialUniforms.sizeof 217 | }, 218 | { 219 | binding: 1, 220 | sampler: material.baseColorTexture.sampler 221 | }, 222 | { 223 | binding: 2, 224 | textureView: material.baseColorTexture.view 225 | }, 226 | { 227 | binding: 3, 228 | sampler: material.normalTexture.sampler 229 | }, 230 | { 231 | binding: 4, 232 | textureView: material.normalTexture.view 233 | }, 234 | { 235 | binding: 5, 236 | sampler: material.roughnessMetallicTexture.sampler 237 | }, 238 | { 239 | binding: 6, 240 | textureView: material.roughnessMetallicTexture.view 241 | } 242 | ]; 243 | WGPUBindGroupDescriptor bindGroupDescriptor = { 244 | label: label.toStringz, 245 | layout: renderer.materialResourceLayout, 246 | entries: entries.ptr, 247 | entryCount: entries.length 248 | }; 249 | _bindGroup = wgpuDeviceCreateBindGroup(renderer.gpu.device, &bindGroupDescriptor); 250 | } 251 | 252 | void upload() 253 | { 254 | wgpuQueueWriteBuffer(renderer.gpu.queue, uniformBuffer, 0, cast(ubyte*)&uniforms, MaterialUniforms.sizeof); 255 | } 256 | 257 | WGPUBindGroup bindGroup() @property 258 | { 259 | return _bindGroup; 260 | } 261 | } 262 | 263 | struct GeometryUniforms 264 | { 265 | mat4 modelViewMatrix; 266 | mat4 normalMatrix; 267 | } 268 | 269 | class GeometryResource: Owner, Resource 270 | { 271 | string label; 272 | Renderer renderer; 273 | GeometryUniforms uniforms; 274 | WGPUBuffer uniformBuffer; 275 | WGPUBindGroup _bindGroup; 276 | 277 | this(Renderer renderer, Owner owner) 278 | { 279 | super(owner); 280 | label = "GeometryResourceUB_" ~ toHash.to!string; 281 | this.renderer = renderer; 282 | 283 | uniforms.modelViewMatrix = mat4.identity; 284 | uniforms.normalMatrix = mat4.identity; 285 | 286 | WGPUBufferDescriptor uniformBufferDescriptor = { 287 | nextInChain: null, 288 | label: label.toStringz, 289 | usage: WGPUBufferUsage.Uniform | WGPUBufferUsage.CopyDst, 290 | size: GeometryUniforms.sizeof, 291 | mappedAtCreation: true 292 | }; 293 | uniformBuffer = wgpuDeviceCreateBuffer(renderer.gpu.device, &uniformBufferDescriptor); 294 | void* destinationPtr = wgpuBufferGetMappedRange(uniformBuffer, 0, GeometryUniforms.sizeof); 295 | memcpy(destinationPtr, cast(ubyte*)&uniforms, GeometryUniforms.sizeof); 296 | wgpuBufferUnmap(uniformBuffer); 297 | 298 | WGPUBindGroupEntry uniformBufferBindGroupEntry = { 299 | nextInChain: null, 300 | binding: 0, 301 | buffer: uniformBuffer, 302 | offset: 0, 303 | size: GeometryUniforms.sizeof, 304 | sampler: null, 305 | textureView: null 306 | }; 307 | 308 | WGPUBindGroupDescriptor bindGroupDescriptor = { 309 | label: label.toStringz, 310 | layout: renderer.geometryResourceLayout, 311 | entries: &uniformBufferBindGroupEntry, 312 | entryCount: 1 313 | }; 314 | 315 | _bindGroup = wgpuDeviceCreateBindGroup(renderer.gpu.device, &bindGroupDescriptor); 316 | } 317 | 318 | void upload() 319 | { 320 | wgpuQueueWriteBuffer(renderer.gpu.queue, uniformBuffer, 0, cast(ubyte*)&uniforms, GeometryUniforms.sizeof); 321 | } 322 | 323 | WGPUBindGroup bindGroup() @property 324 | { 325 | return _bindGroup; 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/dgpu/render/shader.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2025 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.render.shader; 28 | 29 | import std.string; 30 | import std.conv; 31 | import bindbc.wgpu; 32 | import dlib.core.memory; 33 | import dlib.core.ownership; 34 | import dgpu.render.renderer; 35 | 36 | class Shader: Owner 37 | { 38 | Renderer renderer; 39 | string label; 40 | string vertexEntryPoint = "vs_main"; 41 | string fragmentEntryPoint = "fs_main"; 42 | 43 | struct Modules 44 | { 45 | WGPUShaderModule vertex; 46 | WGPUShaderModule fragment; 47 | } 48 | 49 | Modules modules; 50 | 51 | this(string vertexEntryPoint, string fragmentEntryPoint, Renderer renderer, Owner owner) 52 | { 53 | super(owner); 54 | label = toHash.to!string; 55 | this.renderer = renderer; 56 | this.vertexEntryPoint = vertexEntryPoint; 57 | this.fragmentEntryPoint = fragmentEntryPoint; 58 | } 59 | 60 | this(string wgslCode, string vertexEntryPoint, string fragmentEntryPoint, Renderer renderer, Owner owner) 61 | { 62 | this(vertexEntryPoint, fragmentEntryPoint, renderer, owner); 63 | modules.vertex = moduleFromWGSL(wgslCode); 64 | modules.fragment = modules.vertex; 65 | } 66 | 67 | this(uint[] spvCode, string vertexEntryPoint, string fragmentEntryPoint, Renderer renderer, Owner owner) 68 | { 69 | this(vertexEntryPoint, fragmentEntryPoint, renderer, owner); 70 | modules.vertex = moduleFromSPIRV(spvCode); 71 | modules.fragment = modules.vertex; 72 | } 73 | 74 | this(uint[] spvVertCode, string vertexEntryPoint, uint[] spvFragCode, string fragmentEntryPoint, Renderer renderer, Owner owner) 75 | { 76 | this(vertexEntryPoint, fragmentEntryPoint, renderer, owner); 77 | modules.vertex = moduleFromSPIRV(spvVertCode); 78 | modules.fragment = moduleFromSPIRV(spvFragCode); 79 | } 80 | 81 | WGPUShaderModule moduleFromWGSL(string wgslCode) 82 | { 83 | const(char)* shaderText = wgslCode.toStringz; 84 | WGPUShaderModuleWGSLDescriptor wgslDescriptor = { 85 | chain: { 86 | next: null, 87 | sType: WGPUSType.ShaderModuleWGSLDescriptor 88 | }, 89 | code: shaderText 90 | }; 91 | 92 | WGPUShaderModuleDescriptor shaderModuleDescriptor = { 93 | nextInChain: cast(const(WGPUChainedStruct)*)&wgslDescriptor, 94 | label: label.toStringz, 95 | }; 96 | 97 | return wgpuDeviceCreateShaderModule(renderer.gpu.device, &shaderModuleDescriptor); 98 | } 99 | 100 | WGPUShaderModule moduleFromSPIRV(uint[] spvCode) 101 | { 102 | WGPUShaderModuleSPIRVDescriptor spirvDescriptor = { 103 | chain: { 104 | next: null, 105 | sType: WGPUSType.ShaderModuleSPIRVDescriptor 106 | }, 107 | codeSize: cast(uint)spvCode.length, 108 | code: spvCode.ptr 109 | }; 110 | 111 | WGPUShaderModuleDescriptor shaderModuleDescriptor = { 112 | nextInChain: cast(const(WGPUChainedStruct)*)&spirvDescriptor, 113 | label: label.toStringz, 114 | }; 115 | 116 | return wgpuDeviceCreateShaderModule(renderer.gpu.device, &shaderModuleDescriptor); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/dgpu/render/target.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2024 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.render.target; 28 | 29 | import bindbc.wgpu; 30 | import dlib.core.ownership; 31 | import dgpu.core.gpu; 32 | 33 | struct RenderBuffer 34 | { 35 | WGPUTextureView view; 36 | uint width; 37 | uint height; 38 | } 39 | 40 | interface RenderTarget 41 | { 42 | RenderBuffer nextBackBuffer(); 43 | RenderBuffer depthStencilBuffer(); 44 | WGPUTextureFormat colorFormat(); 45 | WGPUTextureFormat depthStencilFormat(); 46 | } 47 | 48 | class ScreenRenderTarget: Owner, RenderTarget 49 | { 50 | GPU gpu; 51 | WGPUTextureFormat surfaceFormat; 52 | protected WGPUTextureView _depthStencilBuffer; 53 | uint width; 54 | uint height; 55 | float aspectRatio; 56 | 57 | this(GPU gpu, uint width, uint height, Owner o) 58 | { 59 | super(o); 60 | this.gpu = gpu; 61 | surfaceFormat = WGPUTextureFormat.BGRA8UnormSrgb; //wgpuSurfaceGetPreferredFormat(gpu.surface, gpu.adapter); 62 | this.width = width; 63 | this.height = height; 64 | this.aspectRatio = cast(float)width / cast(float)height; 65 | configureSurface(surfaceFormat, width, height); 66 | _depthStencilBuffer = createDepthStencilBuffer(width, height); 67 | } 68 | 69 | protected void configureSurface(WGPUTextureFormat format, uint w, uint h) { 70 | WGPUSurfaceConfiguration config = { 71 | nextInChain: null, 72 | device: gpu.device, 73 | format: format, 74 | usage: WGPUTextureUsage.RenderAttachment, 75 | viewFormatCount: 0, 76 | viewFormats: null, 77 | alphaMode: WGPUCompositeAlphaMode.Auto, 78 | width: w, 79 | height: h, 80 | presentMode: WGPUPresentMode.Fifo 81 | }; 82 | wgpuSurfaceConfigure(gpu.surface, &config); 83 | } 84 | 85 | protected WGPUTextureView createDepthStencilBuffer(uint width, uint height) 86 | { 87 | WGPUTextureDescriptor depthTextureDescriptor = { 88 | nextInChain: null, 89 | label: "depthTextureDescriptor0", 90 | usage: WGPUTextureUsage.RenderAttachment, 91 | dimension: WGPUTextureDimension.D2, 92 | size: WGPUExtent3D(width, height, 1), 93 | format: WGPUTextureFormat.Depth24PlusStencil8, 94 | mipLevelCount: 1, 95 | sampleCount: 1 96 | }; 97 | WGPUTexture depthTexture = wgpuDeviceCreateTexture(gpu.device, &depthTextureDescriptor); 98 | 99 | WGPUTextureViewDescriptor depthTextureViewDescriptor = { 100 | nextInChain: null, 101 | label: "depthTextureViewDescriptor0", 102 | format: WGPUTextureFormat.Depth24PlusStencil8, 103 | dimension: WGPUTextureViewDimension.D2, 104 | baseMipLevel: 0, 105 | mipLevelCount: 1, 106 | baseArrayLayer: 0, 107 | arrayLayerCount: 1, 108 | aspect: WGPUTextureAspect.All 109 | }; 110 | WGPUTextureView depthTextureView = wgpuTextureCreateView(depthTexture, &depthTextureViewDescriptor); 111 | 112 | return depthTextureView; 113 | } 114 | 115 | RenderBuffer nextBackBuffer() 116 | { 117 | WGPUSurfaceTexture surfaceTexture; 118 | wgpuSurfaceGetCurrentTexture(gpu.surface, &surfaceTexture); 119 | while (surfaceTexture.status != WGPUSurfaceGetCurrentTextureStatus.Success) {} 120 | WGPUTextureView nextTextureView = wgpuTextureCreateView(surfaceTexture.texture, null); 121 | return RenderBuffer(nextTextureView, width, height); 122 | } 123 | 124 | RenderBuffer depthStencilBuffer() 125 | { 126 | return RenderBuffer(_depthStencilBuffer, width, height); 127 | } 128 | 129 | WGPUTextureFormat colorFormat() 130 | { 131 | return surfaceFormat; 132 | } 133 | 134 | WGPUTextureFormat depthStencilFormat() 135 | { 136 | return WGPUTextureFormat.Depth24PlusStencil8; 137 | } 138 | 139 | void resize(uint width, uint height) 140 | { 141 | this.width = width; 142 | this.height = height; 143 | this.aspectRatio = cast(float)width / cast(float)height; 144 | configureSurface(surfaceFormat, width, height); 145 | _depthStencilBuffer = createDepthStencilBuffer(width, height); 146 | } 147 | 148 | void present() 149 | { 150 | wgpuSurfacePresent(gpu.surface); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/dgpu/render/vertexbuffer.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module dgpu.render.vertexbuffer; 28 | 29 | import core.stdc.string; 30 | import std.string; 31 | import std.conv; 32 | import bindbc.wgpu; 33 | import dlib.core.memory; 34 | import dlib.core.ownership; 35 | import dlib.math.vector; 36 | import dgpu.core.gpu; 37 | import dgpu.asset.entity; 38 | import dgpu.asset.trimesh; 39 | import dgpu.render.renderer; 40 | import dgpu.render.drawable; 41 | 42 | struct VertexAttribute 43 | { 44 | Vector3f v; 45 | Vector2f t; 46 | Vector3f n; 47 | } 48 | 49 | class VertexBuffer: Drawable 50 | { 51 | string label; 52 | 53 | WGPUBuffer attributeBuffer; 54 | size_t attributesSize; 55 | size_t numVertices; 56 | 57 | WGPUBuffer indexBuffer; 58 | size_t indicesSize; 59 | size_t numIndices; 60 | 61 | this(TriangleMesh mesh, Renderer renderer, Owner o) 62 | { 63 | super(o); 64 | label = toHash.to!string; 65 | 66 | Vector3f[] vertices = mesh.vertices; 67 | Vector2f[] texcoords = mesh.texcoords; 68 | Vector3f[] normals = mesh.normals; 69 | uint[] indices = mesh.indices; 70 | 71 | numVertices = vertices.length; 72 | numIndices = indices.length; 73 | 74 | VertexAttribute[] attributes = New!(VertexAttribute[])(numVertices); 75 | for (size_t i = 0; i < numVertices; i++) 76 | { 77 | attributes[i].v = vertices[i]; 78 | attributes[i].t = texcoords[i]; 79 | attributes[i].n = normals[i]; 80 | } 81 | 82 | //uint[] _indices = indices; 83 | //size_t s = indices.length * uint.sizeof; 84 | /* 85 | if (s % 4 != 0) // Should always be aligned to 4 bytes 86 | { 87 | _indices = New!(uint[])(indices.length + 1); 88 | _indices[0..indices.length] = indices[]; 89 | } 90 | */ 91 | 92 | attributesSize = cast(size_t)attributes.length * VertexAttribute.sizeof; 93 | WGPUBufferDescriptor attributeBufferDescriptor = { 94 | nextInChain: null, 95 | label: ("AttributeBuffer_" ~ label).toStringz, 96 | usage: WGPUBufferUsage.Vertex | WGPUBufferUsage.CopyDst, 97 | size: attributesSize, 98 | mappedAtCreation: true 99 | }; 100 | attributeBuffer = wgpuDeviceCreateBuffer(renderer.gpu.device, &attributeBufferDescriptor); 101 | void* attributesPtr = wgpuBufferGetMappedRange(attributeBuffer, 0, attributesSize); 102 | memcpy(attributesPtr, attributes.ptr, attributesSize); 103 | wgpuBufferUnmap(attributeBuffer); 104 | 105 | indicesSize = cast(size_t)indices.length * uint.sizeof; 106 | WGPUBufferDescriptor indexBufferDescriptor = { 107 | nextInChain: null, 108 | label: ("IndexBuffer_" ~ label).toStringz, 109 | usage: WGPUBufferUsage.Index | WGPUBufferUsage.CopyDst, 110 | size: indicesSize, 111 | mappedAtCreation: true 112 | }; 113 | indexBuffer = wgpuDeviceCreateBuffer(renderer.gpu.device, &indexBufferDescriptor); 114 | void* indicesPtr = wgpuBufferGetMappedRange(indexBuffer, 0, indicesSize); 115 | memcpy(indicesPtr, indices.ptr, indicesSize); 116 | wgpuBufferUnmap(indexBuffer); 117 | 118 | /* 119 | if (s % 4 != 0) 120 | { 121 | Delete(_indices); 122 | } 123 | */ 124 | 125 | Delete(attributes); 126 | } 127 | 128 | override void draw(Entity entity, WGPURenderPassEncoder encoder) 129 | { 130 | wgpuRenderPassEncoderSetVertexBuffer(encoder, 0, attributeBuffer, 0, WGPU_WHOLE_SIZE); 131 | wgpuRenderPassEncoderSetIndexBuffer(encoder, indexBuffer, WGPUIndexFormat.Uint32, 0, WGPU_WHOLE_SIZE); 132 | wgpuRenderPassEncoderDrawIndexed(encoder, cast(uint)numIndices, 1, 0, 0, 0); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/freeview.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2023 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | module freeview; 29 | 30 | import std.stdio; 31 | import std.math; 32 | 33 | import dlib.core.ownership; 34 | import dlib.math.vector; 35 | import dlib.math.matrix; 36 | import dlib.math.quaternion; 37 | import dlib.math.transformation; 38 | import dlib.math.utils; 39 | 40 | import dgpu.core.event; 41 | import dgpu.core.keycodes; 42 | import dgpu.core.time; 43 | 44 | class FreeviewComponent: EventListener 45 | { 46 | int prevMouseX; 47 | int prevMouseY; 48 | float mouseSensibility = 0.01f; 49 | 50 | Vector3f center; 51 | float distance; 52 | Quaternionf rotPitch; 53 | Quaternionf rotTurn; 54 | Quaternionf rotRoll; 55 | Matrix4x4f transform; 56 | Matrix4x4f invTransform; 57 | 58 | float rotPitchTheta = 0.0f; 59 | float rotTurnTheta = 0.0f; 60 | float rotRollTheta = 0.0f; 61 | 62 | float pitchCurrentTheta = 0.0f; 63 | float pitchTargetTheta = 0.0f; 64 | float turnCurrentTheta = 0.0f; 65 | float turnTargetTheta = 0.0f; 66 | float rollCurrentTheta = 0.0f; 67 | float rollTargetTheta = 0.0f; 68 | 69 | float currentMove = 0.0f; 70 | float targetMove = 0.0f; 71 | 72 | float currentStrafe = 0.0f; 73 | float targetStrafe = 0.0f; 74 | 75 | float currentZoom = 0.0f; 76 | float targetZoom = 0.0f; 77 | 78 | bool zoomIn = false; 79 | float zoomSmoothFactor = 2.0f; 80 | float translateSmoothFactor = 10.0f; 81 | 82 | Vector3f currentTranslate; 83 | Vector3f targetTranslate; 84 | 85 | bool movingToTarget = false; 86 | 87 | bool active = true; 88 | 89 | this(EventManager em, Owner o) 90 | { 91 | super(em, o); 92 | 93 | center = Vector3f(0.0f, 0.0f, 0.0f); 94 | rotPitch = rotationQuaternion(Vector3f(1.0f,0.0f,0.0f), 0.0f); 95 | rotTurn = rotationQuaternion(Vector3f(0.0f,1.0f,0.0f), 0.0f); 96 | rotRoll = rotationQuaternion(Vector3f(0.0f,0.0f,1.0f), 0.0f); 97 | transform = Matrix4x4f.identity; 98 | invTransform = Matrix4x4f.identity; 99 | distance = 5.0f; 100 | 101 | currentTranslate = Vector3f(0.0f, 0.0f, 0.0f); 102 | targetTranslate = Vector3f(0.0f, 0.0f, 0.0f); 103 | 104 | pitch(45.0f); 105 | turn(45.0f); 106 | setZoom(20.0f); 107 | } 108 | 109 | void reset() 110 | { 111 | center = Vector3f(0.0f, 0.0f, 0.0f); 112 | rotPitch = rotationQuaternion(Vector3f(1.0f,0.0f,0.0f), 0.0f); 113 | rotTurn = rotationQuaternion(Vector3f(0.0f,1.0f,0.0f), 0.0f); 114 | rotRoll = rotationQuaternion(Vector3f(0.0f,0.0f,1.0f), 0.0f); 115 | transform = Matrix4x4f.identity; 116 | invTransform = Matrix4x4f.identity; 117 | distance = 10.0f; 118 | 119 | currentTranslate = Vector3f(0.0f, 0.0f, 0.0f); 120 | targetTranslate = Vector3f(0.0f, 0.0f, 0.0f); 121 | 122 | rotPitchTheta = 0.0f; 123 | rotTurnTheta = 0.0f; 124 | rotRollTheta = 0.0f; 125 | 126 | pitchCurrentTheta = 0.0f; 127 | pitchTargetTheta = 0.0f; 128 | turnCurrentTheta = 0.0f; 129 | turnTargetTheta = 0.0f; 130 | rollCurrentTheta = 0.0f; 131 | rollTargetTheta = 0.0f; 132 | 133 | currentMove = 0.0f; 134 | targetMove = 0.0f; 135 | 136 | currentStrafe = 0.0f; 137 | targetStrafe = 0.0f; 138 | 139 | currentZoom = 0.0f; 140 | targetZoom = 0.0f; 141 | 142 | pitch(45.0f); 143 | turn(45.0f); 144 | setZoom(20.0f); 145 | } 146 | 147 | void update(Time time) 148 | { 149 | processEvents(); 150 | 151 | if (active) 152 | { 153 | if (eventManager.mouseButtonPressed[MB_RIGHT]) 154 | { 155 | float shiftx = (eventManager.mouseX - prevMouseX) * mouseSensibility; 156 | float shifty = -(eventManager.mouseY - prevMouseY) * mouseSensibility; 157 | Vector3f trans = up * shifty + right * shiftx; 158 | translateTarget(trans); 159 | } 160 | else if (eventManager.mouseButtonPressed[MB_LEFT] && eventManager.keyPressed[KEY_LCTRL]) 161 | { 162 | float shiftx = (eventManager.mouseX - prevMouseX); 163 | float shifty = (eventManager.mouseY - prevMouseY); 164 | zoom((shiftx + shifty) * 0.1f); 165 | } 166 | else if (eventManager.mouseButtonPressed[MB_LEFT]) 167 | { 168 | float t = (eventManager.mouseX - prevMouseX); 169 | float p = (eventManager.mouseY - prevMouseY); 170 | pitchSmooth(p, 4.0f); 171 | turnSmooth(t, 4.0f); 172 | } 173 | 174 | prevMouseX = eventManager.mouseX; 175 | prevMouseY = eventManager.mouseY; 176 | } 177 | 178 | if (currentZoom < targetZoom) 179 | { 180 | currentZoom += (targetZoom - currentZoom) / zoomSmoothFactor; 181 | if (zoomIn) 182 | zoom((targetZoom - currentZoom) / zoomSmoothFactor); 183 | else 184 | zoom(-(targetZoom - currentZoom) / zoomSmoothFactor); 185 | } 186 | if (currentTranslate != targetTranslate) 187 | { 188 | Vector3f t = (targetTranslate - currentTranslate) / translateSmoothFactor; 189 | currentTranslate += t; 190 | translateTarget(t); 191 | } 192 | 193 | rotPitch = rotationQuaternion(Vector3f(1.0f,0.0f,0.0f), degtorad(rotPitchTheta)); 194 | rotTurn = rotationQuaternion(Vector3f(0.0f,1.0f,0.0f), degtorad(rotTurnTheta)); 195 | rotRoll = rotationQuaternion(Vector3f(0.0f,0.0f,1.0f), degtorad(rotRollTheta)); 196 | 197 | Quaternionf q = rotPitch * rotTurn * rotRoll; 198 | Matrix4x4f rot = q.toMatrix4x4(); 199 | invTransform = translationMatrix(Vector3f(0.0f, 0.0f, -distance)) * rot * translationMatrix(center); 200 | 201 | transform = invTransform.inverse; 202 | 203 | /* 204 | entity.prevTransformation = entity.transformation; 205 | entity.transformation = transform; 206 | entity.invTransformation = invTransform; 207 | 208 | entity.absoluteTransformation = entity.transformation; 209 | entity.invAbsoluteTransformation = entity.invTransformation; 210 | entity.prevAbsoluteTransformation = entity.prevTransformation; 211 | */ 212 | } 213 | 214 | void setRotation(float p, float t, float r) 215 | { 216 | rotPitchTheta = p; 217 | rotTurnTheta = t; 218 | rotRollTheta = r; 219 | } 220 | 221 | void pitch(float theta) 222 | { 223 | rotPitchTheta += theta; 224 | } 225 | 226 | void turn(float theta) 227 | { 228 | rotTurnTheta += theta; 229 | } 230 | 231 | void roll(float theta) 232 | { 233 | rotRollTheta += theta; 234 | } 235 | 236 | float pitch() 237 | { 238 | return rotPitchTheta; 239 | } 240 | 241 | float turn() 242 | { 243 | return rotTurnTheta; 244 | } 245 | 246 | float roll() 247 | { 248 | return rotRollTheta; 249 | } 250 | 251 | void pitchSmooth(float theta, float smooth) 252 | { 253 | pitchTargetTheta += theta; 254 | float pitchTheta = (pitchTargetTheta - pitchCurrentTheta) / smooth; 255 | pitchCurrentTheta += pitchTheta; 256 | pitch(pitchTheta); 257 | } 258 | 259 | void turnSmooth(float theta, float smooth) 260 | { 261 | turnTargetTheta += theta; 262 | float turnTheta = (turnTargetTheta - turnCurrentTheta) / smooth; 263 | turnCurrentTheta += turnTheta; 264 | turn(turnTheta); 265 | } 266 | 267 | void rollSmooth(float theta, float smooth) 268 | { 269 | rollTargetTheta += theta; 270 | float rollTheta = (rollTargetTheta - rollCurrentTheta) / smooth; 271 | rollCurrentTheta += rollTheta; 272 | roll(rollTheta); 273 | } 274 | 275 | void setTargetSmooth(Vector3f pos, float smooth) 276 | { 277 | currentTranslate = center; 278 | targetTranslate = -pos; 279 | } 280 | 281 | void translateTarget(Vector3f pos) 282 | { 283 | center += pos; 284 | } 285 | 286 | void setZoom(float z) 287 | { 288 | distance = z; 289 | } 290 | 291 | void zoom(float z) 292 | { 293 | distance -= z; 294 | } 295 | 296 | void zoomSmooth(float z, float smooth) 297 | { 298 | zoomSmoothFactor = smooth; 299 | 300 | if (z < 0) 301 | zoomIn = true; 302 | else 303 | zoomIn = false; 304 | 305 | targetZoom += abs(z); 306 | } 307 | 308 | Vector3f position() 309 | { 310 | return transform.translation(); 311 | } 312 | 313 | Vector3f right() 314 | { 315 | return transform.right(); 316 | } 317 | 318 | Vector3f up() 319 | { 320 | return transform.up(); 321 | } 322 | 323 | Vector3f direction() 324 | { 325 | return transform.forward(); 326 | } 327 | 328 | void strafe(float speed) 329 | { 330 | Vector3f forward; 331 | forward.x = cos(degtorad(rotTurnTheta)); 332 | forward.y = 0.0f; 333 | forward.z = sin(degtorad(rotTurnTheta)); 334 | center += forward * speed; 335 | } 336 | 337 | void strafeSmooth(float speed, float smooth) 338 | { 339 | targetMove += speed; 340 | float movesp = (targetMove - currentMove) / smooth; 341 | currentMove += movesp; 342 | strafe(movesp); 343 | } 344 | 345 | void move(float speed) 346 | { 347 | Vector3f dir; 348 | dir.x = cos(degtorad(rotTurnTheta + 90.0f)); 349 | dir.y = 0.0f; 350 | dir.z = sin(degtorad(rotTurnTheta + 90.0f)); 351 | center += dir * speed; 352 | } 353 | 354 | void moveSmooth(float speed, float smooth) 355 | { 356 | targetStrafe += speed; 357 | float strafesp = (targetStrafe - currentStrafe) / smooth; 358 | currentStrafe += strafesp; 359 | move(strafesp); 360 | } 361 | 362 | void screenToWorld( 363 | int scrx, 364 | int scry, 365 | int scrw, 366 | int scrh, 367 | float yfov, 368 | ref float worldx, 369 | ref float worldy, 370 | bool snap) 371 | { 372 | Vector3f camPos = position(); 373 | Vector3f camDir = direction(); 374 | 375 | float aspect = cast(float)scrw / cast(float)scrh; 376 | 377 | float xfov = fovXfromY(yfov, aspect); 378 | 379 | float tfov1 = tan(yfov*PI/360.0f); 380 | float tfov2 = tan(xfov*PI/360.0f); 381 | 382 | Vector3f camUp = up() * tfov1; 383 | Vector3f camRight = right() * tfov2; 384 | 385 | float width = 1.0f - 2.0f * cast(float)(scrx) / cast(float)(scrw); 386 | float height = 1.0f - 2.0f * cast(float)(scry) / cast(float)(scrh); 387 | 388 | float mx = camDir.x + camUp.x * height + camRight.x * width; 389 | float my = camDir.y + camUp.y * height + camRight.y * width; 390 | float mz = camDir.z + camUp.z * height + camRight.z * width; 391 | 392 | worldx = snap? floor(camPos.x - mx * camPos.y / my) : (camPos.x - mx * camPos.y / my); 393 | worldy = snap? floor(camPos.z - mz * camPos.y / my) : (camPos.z - mz * camPos.y / my); 394 | } 395 | 396 | override void onMouseButtonDown(int button) 397 | { 398 | if (!active) 399 | return; 400 | 401 | if (button == MB_LEFT) 402 | { 403 | prevMouseX = eventManager.mouseX; 404 | prevMouseY = eventManager.mouseY; 405 | } 406 | } 407 | 408 | override void onMouseWheel(int x, int y) 409 | { 410 | if (!active) 411 | return; 412 | 413 | zoom(cast(float)y * 0.2f); 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /src/main.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2025 Timur Gafarov 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | */ 27 | module main; 28 | 29 | import std.stdio; 30 | import std.conv; 31 | import std.string; 32 | import core.stdc.string: memcpy; 33 | import dlib.core.ownership; 34 | import dlib.core.memory; 35 | import dlib.math.vector; 36 | import dlib.image.color; 37 | import dlib.image.image; 38 | import bindbc.wgpu; 39 | import dgpu.core.application; 40 | import dgpu.core.time; 41 | import dgpu.core.gpu; 42 | import dgpu.render.renderer; 43 | import dgpu.render.vertexbuffer; 44 | import dgpu.asset.scene; 45 | import dgpu.asset.entity; 46 | import dgpu.asset.trimesh; 47 | import dgpu.asset.io.obj; 48 | import dgpu.asset.image; 49 | import dgpu.asset.texture; 50 | import freeview; 51 | 52 | class MyApplication: Application 53 | { 54 | Renderer renderer; 55 | Scene scene; 56 | Entity eCerberus; 57 | FreeviewComponent view; 58 | 59 | this(uint winWidth, uint winHeight, bool fullscreen, string windowTitle, string[] args) 60 | { 61 | super(winWidth, winHeight, fullscreen, windowTitle, args); 62 | renderer = New!Renderer(this, this); 63 | scene = New!Scene(this); 64 | auto img = image(128, 128, 4); 65 | Texture defaultTexture = New!Texture(renderer.gpu, img, this); 66 | scene.defaultMaterial.baseColorTexture = defaultTexture; 67 | scene.defaultMaterial.normalTexture = defaultTexture; 68 | scene.defaultMaterial.roughnessMetallicTexture = defaultTexture; 69 | 70 | SuperImage cerberusAlbedo = loadImage("data/cerberus-albedo.png"); 71 | SuperImage cerberusNormal = loadImage("data/cerberus-normal.png"); 72 | SuperImage cerberusRM = loadImage("data/cerberus-roughness-metallic.png"); 73 | 74 | Texture texCerberusAlbedo = New!Texture(renderer.gpu, cerberusAlbedo, this); 75 | Texture texCerberusNormal = New!Texture(renderer.gpu, cerberusNormal, this); 76 | Texture texCerberusRoughnessMetallic = New!Texture(renderer.gpu, cerberusRM, this); 77 | 78 | auto istrm = fs.openForInput("data/cerberus.obj"); 79 | auto res = loadOBJ(istrm); 80 | Delete(istrm); 81 | 82 | auto mesh = res[0]; 83 | if (mesh) 84 | { 85 | VertexBuffer cerberus = New!VertexBuffer(mesh, renderer, this); 86 | Delete(mesh); 87 | 88 | eCerberus = scene.createEntity(); 89 | eCerberus.geometry = cerberus; 90 | eCerberus.material = scene.createMaterial(); 91 | eCerberus.material.baseColorFactor = Color4f(1.0, 0.5, 0.0, 1.0); 92 | eCerberus.material.baseColorTexture = texCerberusAlbedo; 93 | eCerberus.material.normalTexture = texCerberusNormal; 94 | eCerberus.material.roughnessMetallicTexture = texCerberusRoughnessMetallic; 95 | } 96 | else 97 | { 98 | writeln(res[1]); 99 | } 100 | 101 | view = New!FreeviewComponent(eventManager, this); 102 | } 103 | 104 | override void onUpdate(Time t) 105 | { 106 | view.update(t); 107 | 108 | scene.update(t); 109 | scene.activeCamera.modelMatrix = view.invTransform; 110 | renderer.update(t); 111 | } 112 | 113 | override void onRender() 114 | { 115 | renderer.renderScene(scene); 116 | } 117 | } 118 | 119 | void main(string[] args) 120 | { 121 | MyApplication app = New!MyApplication(1280, 720, false, "WebGPU Cerberus demo", args); 122 | app.run(); 123 | Delete(app); 124 | writefln("Leaked memory: %s byte(s)", allocatedMemory); 125 | } 126 | --------------------------------------------------------------------------------