Compare commits
12 Commits
40fcba9323
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f357b4e87 | |||
| ea94b65f06 | |||
| a6d2538352 | |||
| 2fb4e38209 | |||
| e8674a6bcd | |||
| a18fcb44ac | |||
| 609321af2a | |||
| c3fa7115c8 | |||
| ff72311979 | |||
| 183e825827 | |||
| f7576a941d | |||
| 8d5a2db342 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,2 +1,3 @@
|
|||||||
*.hdr filter=lfs diff=lfs merge=lfs -text
|
*.hdr filter=lfs diff=lfs merge=lfs -text
|
||||||
*.png filter=lfs diff=lfs merge=lfs -text
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,3 @@
|
|||||||
.zig-cache
|
.zig-cache
|
||||||
zig-out
|
zig-out
|
||||||
zig-pkg
|
zig-pkg
|
||||||
*.spv
|
|
||||||
|
|||||||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
|
"shader-slang.slang-language-extension",
|
||||||
"vadimcn.vscode-lldb",
|
"vadimcn.vscode-lldb",
|
||||||
"ziglang.vscode-zig",
|
"ziglang.vscode-zig",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
{
|
{
|
||||||
"walls": {
|
"walls": {
|
||||||
"positive_x": {
|
"positive_x": {
|
||||||
"material": "OakLogTop.json"
|
"material": "OakLogTop.json",
|
||||||
|
"transform": "identity"
|
||||||
},
|
},
|
||||||
"negative_x": {
|
"negative_x": {
|
||||||
"material": "OakLogTop.json"
|
"material": "OakLogTop.json",
|
||||||
|
"transform": "mirror"
|
||||||
},
|
},
|
||||||
"positive_y": {
|
"positive_y": {
|
||||||
"material": "OakLog.json"
|
"material": "OakLog.json",
|
||||||
|
"transform": "rotate_ccw"
|
||||||
},
|
},
|
||||||
"negative_y": {
|
"negative_y": {
|
||||||
"material": "OakLog.json"
|
"material": "OakLog.json",
|
||||||
|
"transform": "rotate_cw"
|
||||||
},
|
},
|
||||||
"positive_z": {
|
"positive_z": {
|
||||||
"material": "OakLog.json"
|
"material": "OakLog.json",
|
||||||
|
"transform": "rotate_cw"
|
||||||
},
|
},
|
||||||
"negative_z": {
|
"negative_z": {
|
||||||
"material": "OakLog.json"
|
"material": "OakLog.json",
|
||||||
|
"transform": "rotate_cw"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
{
|
{
|
||||||
"walls": {
|
"walls": {
|
||||||
"positive_x": {
|
"positive_x": {
|
||||||
"material": "OakLog.json"
|
"material": "OakLog.json",
|
||||||
|
"transform": "rotate_cw"
|
||||||
},
|
},
|
||||||
"negative_x": {
|
"negative_x": {
|
||||||
"material": "OakLog.json"
|
"material": "OakLog.json",
|
||||||
|
"transform": "rotate_ccw"
|
||||||
},
|
},
|
||||||
"positive_y": {
|
"positive_y": {
|
||||||
"material": "OakLogTop.json"
|
"material": "OakLogTop.json",
|
||||||
|
"transform": "identity"
|
||||||
},
|
},
|
||||||
"negative_y": {
|
"negative_y": {
|
||||||
"material": "OakLogTop.json"
|
"material": "OakLogTop.json",
|
||||||
|
"transform": "mirror"
|
||||||
},
|
},
|
||||||
"positive_z": {
|
"positive_z": {
|
||||||
"material": "OakLog.json"
|
"material": "OakLog.json",
|
||||||
|
"transform": "identity"
|
||||||
},
|
},
|
||||||
"negative_z": {
|
"negative_z": {
|
||||||
"material": "OakLog.json"
|
"material": "OakLog.json",
|
||||||
|
"transform": "rotate_180"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
{
|
{
|
||||||
"walls": {
|
"walls": {
|
||||||
"positive_x": {
|
"positive_x": {
|
||||||
"material": "OakLog.json"
|
"material": "OakLog.json",
|
||||||
|
"transform": "identity"
|
||||||
},
|
},
|
||||||
"negative_x": {
|
"negative_x": {
|
||||||
"material": "OakLog.json"
|
"material": "OakLog.json",
|
||||||
|
"transform": "identity"
|
||||||
},
|
},
|
||||||
"positive_y": {
|
"positive_y": {
|
||||||
"material": "OakLog.json"
|
"material": "OakLog.json",
|
||||||
|
"transform": "identity"
|
||||||
},
|
},
|
||||||
"negative_y": {
|
"negative_y": {
|
||||||
"material": "OakLog.json"
|
"material": "OakLog.json",
|
||||||
|
"transform": "identity"
|
||||||
},
|
},
|
||||||
"positive_z": {
|
"positive_z": {
|
||||||
"material": "OakLogTop.json"
|
"material": "OakLogTop.json",
|
||||||
|
"transform": "identity"
|
||||||
},
|
},
|
||||||
"negative_z": {
|
"negative_z": {
|
||||||
"material": "OakLogTop.json"
|
"material": "OakLogTop.json",
|
||||||
|
"transform": "mirror_rotate_180"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
assets/fonts/EBGaramond-Regular.ttf
(Stored with Git LFS)
Normal file
BIN
assets/fonts/EBGaramond-Regular.ttf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/fonts/FiraGO-Regular.ttf
(Stored with Git LFS)
Normal file
BIN
assets/fonts/FiraGO-Regular.ttf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/fonts/FiraMono-Regular.ttf
(Stored with Git LFS)
Normal file
BIN
assets/fonts/FiraMono-Regular.ttf
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -1,68 +0,0 @@
|
|||||||
#version 460
|
|
||||||
|
|
||||||
layout(set = 0, binding = 0) uniform sampler _EquirectangularSampler;
|
|
||||||
|
|
||||||
layout(set = 0, binding = 1) uniform texture2D _EquirectangularTexture;
|
|
||||||
layout(set = 0, binding = 2, rgba16f) uniform writeonly restrict imageCube _CubemapImage;
|
|
||||||
|
|
||||||
const float INV_PI = 0.31830987;
|
|
||||||
const float HALF_INV_PI = 0.15915494;
|
|
||||||
|
|
||||||
const mat3x3 MATRIX_2D_TO_CUBE[6] = mat3x3[](
|
|
||||||
// Positive X
|
|
||||||
mat3x3(
|
|
||||||
0, 0, -1,
|
|
||||||
0, -1, 0,
|
|
||||||
1, 0, 0
|
|
||||||
),
|
|
||||||
// Negative X
|
|
||||||
mat3x3(
|
|
||||||
0, 0, 1,
|
|
||||||
0, -1, 0,
|
|
||||||
-1, 0, 0
|
|
||||||
),
|
|
||||||
// Positive Y
|
|
||||||
mat3x3(
|
|
||||||
1, 0, 0,
|
|
||||||
0, 0, 1,
|
|
||||||
0, 1, 0
|
|
||||||
),
|
|
||||||
// Negative Y
|
|
||||||
mat3x3(
|
|
||||||
1, 0, 0,
|
|
||||||
0, 0, -1,
|
|
||||||
0, -1, 0
|
|
||||||
),
|
|
||||||
// Positive Z
|
|
||||||
mat3x3(
|
|
||||||
1, 0, 0,
|
|
||||||
0, -1, 0,
|
|
||||||
0, 0, 1
|
|
||||||
),
|
|
||||||
// Negative Z
|
|
||||||
mat3x3(
|
|
||||||
-1, 0, 0,
|
|
||||||
0, -1, 0,
|
|
||||||
0, 0, -1
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
|
||||||
void main() {
|
|
||||||
vec2 size = vec2(imageSize(_CubemapImage).xy);
|
|
||||||
vec2 texCoord = (vec2(gl_GlobalInvocationID.xy) + vec2(0.5)) / size;
|
|
||||||
uint layerIndex = gl_GlobalInvocationID.z;
|
|
||||||
|
|
||||||
texCoord = texCoord * vec2(2.0) - vec2(1.0); // Map to range [-1, 1]
|
|
||||||
|
|
||||||
vec3 cubeCoord = MATRIX_2D_TO_CUBE[layerIndex] * vec3(texCoord, 1.0);
|
|
||||||
vec3 cubeDir = normalize(cubeCoord);
|
|
||||||
|
|
||||||
float theta = atan(cubeDir.y, cubeDir.x);
|
|
||||||
float phi = asin(cubeDir.z);
|
|
||||||
|
|
||||||
vec2 equirectCoord = vec2(theta * HALF_INV_PI, phi * INV_PI) + vec2(0.5);
|
|
||||||
|
|
||||||
vec4 irradiance = texture(sampler2D(_EquirectangularTexture, _EquirectangularSampler), equirectCoord);
|
|
||||||
imageStore(_CubemapImage, ivec3(gl_GlobalInvocationID.xyz), irradiance);
|
|
||||||
}
|
|
||||||
72
assets/shaders/equirect_to_cube.slang
Normal file
72
assets/shaders/equirect_to_cube.slang
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import "includes/math";
|
||||||
|
|
||||||
|
[[vk::binding(0, 0)]] SamplerState _EquirectangularSampler;
|
||||||
|
[[vk::binding(1, 0)]] Texture2D _EquirectangularTexture;
|
||||||
|
[[vk::binding(2, 0)]] WTexture2DArray _CubemapImage;
|
||||||
|
|
||||||
|
static const float3x3 MATRIX_2D_TO_CUBE[6] = {
|
||||||
|
// Positive X
|
||||||
|
{
|
||||||
|
0, 0, 1,
|
||||||
|
0, -1, 0,
|
||||||
|
-1, 0, 0,
|
||||||
|
},
|
||||||
|
// Negative X
|
||||||
|
{
|
||||||
|
0, 0, -1,
|
||||||
|
0, -1, 0,
|
||||||
|
1, 0, 0,
|
||||||
|
},
|
||||||
|
// Positive Y
|
||||||
|
{
|
||||||
|
1, 0, 0,
|
||||||
|
0, 0, 1,
|
||||||
|
0, 1, 0,
|
||||||
|
},
|
||||||
|
// Negative Y
|
||||||
|
{
|
||||||
|
1, 0, 0,
|
||||||
|
0, 0, -1,
|
||||||
|
0, -1, 0,
|
||||||
|
},
|
||||||
|
// Positive Z
|
||||||
|
{
|
||||||
|
1, 0, 0,
|
||||||
|
0, -1, 0,
|
||||||
|
0, 0, 1,
|
||||||
|
},
|
||||||
|
// Negative Z
|
||||||
|
{
|
||||||
|
-1, 0, 0,
|
||||||
|
0, -1, 0,
|
||||||
|
0, 0, -1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
float2 cubemapSize() {
|
||||||
|
uint width, height, elements;
|
||||||
|
_CubemapImage.GetDimensions(width, height, elements);
|
||||||
|
return float2(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("compute")]
|
||||||
|
[numthreads(8, 8, 1)]
|
||||||
|
void main(uint3 threadID: SV_DispatchThreadID)
|
||||||
|
{
|
||||||
|
let size = cubemapSize();
|
||||||
|
var texCoord = (float2(threadID.xy) + 0.5) / size;
|
||||||
|
let layerIndex = threadID.z;
|
||||||
|
|
||||||
|
texCoord = 2.0 * texCoord - 1.0; // Map to range [-1, 1]
|
||||||
|
|
||||||
|
let cubeCoord = mul(MATRIX_2D_TO_CUBE[layerIndex], float3(texCoord, 1.0));
|
||||||
|
let cubeDir = normalize(cubeCoord);
|
||||||
|
|
||||||
|
let theta = atan2(cubeDir.y, cubeDir.x);
|
||||||
|
let phi = asin(cubeDir.z);
|
||||||
|
|
||||||
|
let equirectCoord = float2(theta * HALF_INV_PI, phi * INV_PI) + 0.5;
|
||||||
|
|
||||||
|
let irradiance = _EquirectangularTexture.SampleLevel(_EquirectangularSampler, equirectCoord, 0.0);
|
||||||
|
_CubemapImage.Store(threadID, irradiance);
|
||||||
|
}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#version 460
|
|
||||||
#extension GL_EXT_nonuniform_qualifier : require
|
|
||||||
#extension GL_EXT_scalar_block_layout : require
|
|
||||||
#extension GL_EXT_shader_16bit_storage : require
|
|
||||||
|
|
||||||
in Varyings {
|
|
||||||
layout(location = 0) flat uint instance;
|
|
||||||
layout(location = 1) vec2 positionSSPX;
|
|
||||||
} var;
|
|
||||||
|
|
||||||
#include "includes/gui_box_common.glsl"
|
|
||||||
|
|
||||||
layout(location = 0) out vec4 fragColor;
|
|
||||||
|
|
||||||
float boxSDF(vec2 p, vec2 center, vec2 halfExtents, float borderRadius) {
|
|
||||||
vec2 q = abs(p - center) - halfExtents + borderRadius;
|
|
||||||
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - borderRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define BOX _Boxes[var.instance]
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 halfExtentsPX = 0.5 * BOX.sizePX;
|
|
||||||
vec2 centerSSPX = BOX.positionSSPX + halfExtentsPX;
|
|
||||||
float borderHalfWidthPX = 0.5 * BOX.borderWidthPX;
|
|
||||||
|
|
||||||
float interiorSDF = boxSDF(var.positionSSPX, centerSSPX, halfExtentsPX, BOX.borderRadiusPX) + BOX.borderWidthPX;
|
|
||||||
float borderSDF = abs(interiorSDF - borderHalfWidthPX) - borderHalfWidthPX;
|
|
||||||
|
|
||||||
float interiorCoverage = clamp(-interiorSDF + 0.5, 0.0, 1.0) * BOX.backgroundColor.a;
|
|
||||||
float borderCoverage = clamp(-borderSDF + 0.5, 0.0, 1.0) * BOX.borderColor.a;
|
|
||||||
float totalCoverage = interiorCoverage + borderCoverage;
|
|
||||||
|
|
||||||
fragColor = vec4(
|
|
||||||
interiorCoverage * BOX.backgroundColor.rgb +
|
|
||||||
borderCoverage * BOX.borderColor.rgb,
|
|
||||||
totalCoverage
|
|
||||||
);
|
|
||||||
}
|
|
||||||
65
assets/shaders/gui_box.slang
Normal file
65
assets/shaders/gui_box.slang
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import "includes/math";
|
||||||
|
import "includes/structs";
|
||||||
|
|
||||||
|
struct Varyings {
|
||||||
|
float4 positionCS : SV_Position;
|
||||||
|
nointerpolation uint instance;
|
||||||
|
float2 positionSSPX;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GuiBox {
|
||||||
|
float4 backgroundColor;
|
||||||
|
float4 borderColor;
|
||||||
|
float2 positionSSPX;
|
||||||
|
float2 sizePX;
|
||||||
|
float borderWidthPX;
|
||||||
|
float borderRadiusPX;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[vk::binding(0, 0)]] ConstantBuffer<GlobalUniforms> _Global;
|
||||||
|
[[vk::binding(1, 0)]] StructuredBuffer<GuiBox> _Boxes;
|
||||||
|
|
||||||
|
static const float2 VERTICES[4] = {
|
||||||
|
float2(0, 1),
|
||||||
|
float2(1, 1),
|
||||||
|
float2(0, 0),
|
||||||
|
float2(1, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
[shader("vertex")]
|
||||||
|
Varyings vertexMain(uint vertexID : SV_VulkanVertexID, uint instanceID : SV_VulkanInstanceID) {
|
||||||
|
Varyings out;
|
||||||
|
|
||||||
|
let vertex = VERTICES[vertexID];
|
||||||
|
let box = _Boxes[instanceID];
|
||||||
|
|
||||||
|
let positionSSPX = vertex * box.sizePX + box.positionSSPX;
|
||||||
|
let positionCS = float4(mul(_Global.matrixSSPXtoCS, float3(positionSSPX, 1.0)), 0.0, 1.0);
|
||||||
|
|
||||||
|
out.positionCS = positionCS;
|
||||||
|
out.instance = instanceID;
|
||||||
|
out.positionSSPX = positionSSPX;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("fragment")]
|
||||||
|
float4 fragmentMain(Varyings frag) : SV_Target {
|
||||||
|
let box = _Boxes[frag.instance];
|
||||||
|
|
||||||
|
let halfExtentsPX = 0.5 * box.sizePX;
|
||||||
|
let centerSSPX = box.positionSSPX + halfExtentsPX;
|
||||||
|
let borderHalfWidthPX = 0.5 * box.borderWidthPX;
|
||||||
|
|
||||||
|
let interiorSDF = boxSDF(frag.positionSSPX, centerSSPX, halfExtentsPX, box.borderRadiusPX) + box.borderWidthPX;
|
||||||
|
float borderSDF = abs(interiorSDF - borderHalfWidthPX) - borderHalfWidthPX;
|
||||||
|
|
||||||
|
float interiorCoverage = saturate(-interiorSDF + 0.5) * box.backgroundColor.a;
|
||||||
|
float borderCoverage = saturate(-borderSDF + 0.5) * box.borderColor.a;
|
||||||
|
float totalCoverage = interiorCoverage + borderCoverage;
|
||||||
|
|
||||||
|
return float4(
|
||||||
|
interiorCoverage * box.backgroundColor.rgb +
|
||||||
|
borderCoverage * box.borderColor.rgb,
|
||||||
|
totalCoverage,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#version 460
|
|
||||||
#extension GL_EXT_nonuniform_qualifier : require
|
|
||||||
#extension GL_EXT_scalar_block_layout : require
|
|
||||||
#extension GL_EXT_shader_16bit_storage : require
|
|
||||||
|
|
||||||
out Varyings {
|
|
||||||
layout(location = 0) flat uint instance;
|
|
||||||
layout(location = 1) vec2 positionSSPX;
|
|
||||||
} var;
|
|
||||||
|
|
||||||
#include "includes/gui_box_common.glsl"
|
|
||||||
|
|
||||||
const vec2 VERTICES[4] = vec2[](
|
|
||||||
vec2(0, 1),
|
|
||||||
vec2(1, 1),
|
|
||||||
vec2(0, 0),
|
|
||||||
vec2(1, 0)
|
|
||||||
);
|
|
||||||
|
|
||||||
#define BOX _Boxes[gl_InstanceIndex]
|
|
||||||
#define VERTEX VERTICES[gl_VertexIndex]
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 positionSSPX = VERTEX * BOX.sizePX + BOX.positionSSPX;
|
|
||||||
vec4 positionCS = vec4(_Global.matrixSSPXtoCS * vec3(positionSSPX, 1.0), 0.0, 1.0);
|
|
||||||
|
|
||||||
gl_Position = positionCS;
|
|
||||||
var.instance = gl_InstanceIndex;
|
|
||||||
var.positionSSPX = positionSSPX;
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#version 460
|
|
||||||
#extension GL_EXT_nonuniform_qualifier : require
|
|
||||||
#extension GL_EXT_scalar_block_layout : require
|
|
||||||
#extension GL_EXT_shader_16bit_storage : require
|
|
||||||
|
|
||||||
in Varyings {
|
|
||||||
layout(location = 0) flat uint instance;
|
|
||||||
layout(location = 1) vec2 texCoord;
|
|
||||||
} var;
|
|
||||||
|
|
||||||
#include "includes/gui_image_common.glsl"
|
|
||||||
|
|
||||||
layout(location = 0) out vec4 fragColor;
|
|
||||||
|
|
||||||
#define IMAGE _Images[var.instance]
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texel = texture(sampler2D(_Textures[uint(IMAGE.textureId)], _Sampler), var.texCoord);
|
|
||||||
fragColor = texel * IMAGE.tint;
|
|
||||||
}
|
|
||||||
54
assets/shaders/gui_image.slang
Normal file
54
assets/shaders/gui_image.slang
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import "includes/math";
|
||||||
|
import "includes/structs";
|
||||||
|
|
||||||
|
struct Varyings {
|
||||||
|
float4 positionCS : SV_Position;
|
||||||
|
nointerpolation uint instance;
|
||||||
|
float2 texCoord;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GuiImage {
|
||||||
|
float4 tint;
|
||||||
|
float2 positionSSPX;
|
||||||
|
float2 sizePX;
|
||||||
|
float2 uvMin;
|
||||||
|
float2 uvMax;
|
||||||
|
uint16_t texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[vk::binding(0, 0)]] ConstantBuffer<GlobalUniforms> _Global;
|
||||||
|
[[vk::binding(1, 0)]] StructuredBuffer<GuiImage> _Images;
|
||||||
|
[[vk::binding(2, 0)]] SamplerState _Sampler;
|
||||||
|
[[vk::binding(3, 0)]] Texture2D _Textures[];
|
||||||
|
|
||||||
|
static const float2 VERTICES[4] = {
|
||||||
|
float2(0, 1),
|
||||||
|
float2(1, 1),
|
||||||
|
float2(0, 0),
|
||||||
|
float2(1, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
[shader("vertex")]
|
||||||
|
Varyings vertexMain(uint vertexID : SV_VulkanVertexID, uint instanceID : SV_VulkanInstanceID) {
|
||||||
|
Varyings out;
|
||||||
|
|
||||||
|
let vertex = VERTICES[vertexID];
|
||||||
|
let image = _Images[instanceID];
|
||||||
|
|
||||||
|
let positionSSPX = vertex * image.sizePX + image.positionSSPX;
|
||||||
|
let positionCS = float4(mul(_Global.matrixSSPXtoCS, float3(positionSSPX, 1.0)), 0.0, 1.0);
|
||||||
|
|
||||||
|
let texCoord = vertex * (image.uvMax - image.uvMin) + image.uvMin;
|
||||||
|
|
||||||
|
out.positionCS = positionCS;
|
||||||
|
out.instance = instanceID;
|
||||||
|
out.texCoord = texCoord;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("fragment")]
|
||||||
|
float4 fragmentMain(Varyings frag) : SV_Target {
|
||||||
|
let image = _Images[frag.instance];
|
||||||
|
let texel = texture2DAA(_Textures[image.texture], _Sampler, frag.texCoord);
|
||||||
|
return texel * image.tint;
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#version 460
|
|
||||||
#extension GL_EXT_nonuniform_qualifier : require
|
|
||||||
#extension GL_EXT_scalar_block_layout : require
|
|
||||||
#extension GL_EXT_shader_16bit_storage : require
|
|
||||||
|
|
||||||
out Varyings {
|
|
||||||
layout(location = 0) flat uint instance;
|
|
||||||
layout(location = 1) vec2 texCoord;
|
|
||||||
} var;
|
|
||||||
|
|
||||||
#include "includes/gui_image_common.glsl"
|
|
||||||
|
|
||||||
const vec2 VERTICES[4] = vec2[](
|
|
||||||
vec2(0, 1),
|
|
||||||
vec2(1, 1),
|
|
||||||
vec2(0, 0),
|
|
||||||
vec2(1, 0)
|
|
||||||
);
|
|
||||||
|
|
||||||
#define IMAGE _Images[gl_InstanceIndex]
|
|
||||||
#define VERTEX VERTICES[gl_VertexIndex]
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 positionSSPX = VERTEX * IMAGE.sizePX + IMAGE.positionSSPX;
|
|
||||||
vec4 positionCS = vec4(_Global.matrixSSPXtoCS * vec3(positionSSPX, 1.0), 0.0, 1.0);
|
|
||||||
|
|
||||||
vec2 texCoord = VERTEX * (IMAGE.uvMax - IMAGE.uvMin) + IMAGE.uvMin;
|
|
||||||
|
|
||||||
gl_Position = positionCS;
|
|
||||||
var.instance = gl_InstanceIndex;
|
|
||||||
var.texCoord = texCoord;
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#version 460
|
|
||||||
#extension GL_EXT_nonuniform_qualifier : require
|
|
||||||
#extension GL_EXT_scalar_block_layout : require
|
|
||||||
#extension GL_EXT_shader_16bit_storage : require
|
|
||||||
|
|
||||||
uint calcRootCode(float y1, float y2, float y3) {
|
|
||||||
uint i1 = floatBitsToUint(y1) >> 31U;
|
|
||||||
uint i2 = floatBitsToUint(y2) >> 30U;
|
|
||||||
uint i3 = floatBitsToUint(y3) >> 29U;
|
|
||||||
|
|
||||||
uint shift = (i2 & 2U) | (i1 & ~2U);
|
|
||||||
shift = (i3 & 4U) | (shift & ~4U);
|
|
||||||
|
|
||||||
return ((0x2E74U >> shift) & 0x0101U);
|
|
||||||
}
|
|
||||||
274
assets/shaders/gui_text.slang
Normal file
274
assets/shaders/gui_text.slang
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
import "includes/structs";
|
||||||
|
|
||||||
|
struct Varyings {
|
||||||
|
float4 positionCS : SV_Position;
|
||||||
|
nointerpolation uint instance;
|
||||||
|
float2 texCoordEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GuiText {
|
||||||
|
float4 color;
|
||||||
|
float2 bottomLeftSSPX;
|
||||||
|
float fontSizePX;
|
||||||
|
// Index into _GlyphData[font]
|
||||||
|
uint16_t glyph;
|
||||||
|
// Index into _GlyphData, _ControlPoints and _BandData
|
||||||
|
uint16_t font;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GlyphData {
|
||||||
|
float2 bottomLeftEM;
|
||||||
|
float2 sizeEM;
|
||||||
|
// Index into _BandData of the first vertical/horizontal band data.
|
||||||
|
// `bandPtr.x` refers to vertical bands and `bandPtr.y` refers to horizontal
|
||||||
|
// bands.
|
||||||
|
uint2 bandPtr;
|
||||||
|
// Number of vertical/horizontal bands. `bandCount.x` is the number of
|
||||||
|
// vertical bands and `bandCount.y` is the number of horizontal bands.
|
||||||
|
uint2 bandCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[vk::binding(0, 0)]] ConstantBuffer<GlobalUniforms> _Global;
|
||||||
|
[[vk::binding(1, 0)]] StructuredBuffer<GuiText> _Instances;
|
||||||
|
[[vk::binding(2, 0)]] StructuredBuffer<GlyphData> _GlyphData[];
|
||||||
|
[[vk::binding(3, 0)]] StructuredBuffer<half2> _ControlPoints[];
|
||||||
|
[[vk::binding(4, 0)]] StructuredBuffer<uint> _BandData[];
|
||||||
|
|
||||||
|
static const float2 VERTICES[4] = {
|
||||||
|
float2(0, 1),
|
||||||
|
float2(1, 1),
|
||||||
|
float2(0, 0),
|
||||||
|
float2(1, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Classify a quadratic Bézier curve based on the sign bits of the ray-relative
|
||||||
|
// Y coordinates. Returns `(t2 << 1) | t1` where `t1` and `t2` are bits
|
||||||
|
// signifying the root eligibility; `t1` possibly adding one to the winding
|
||||||
|
// number and `t2` possibly subtracting one from the winding number.
|
||||||
|
uint classifyQuadCurve(float y0, float y1, float y2)
|
||||||
|
{
|
||||||
|
// ┌─────┰────┬────┬────┰────┬────┐
|
||||||
|
// │class┃ y2 │ y1 │ y0 ┃ t2 │ t1 │
|
||||||
|
// ├─────╂────┼────┼────╂────┼────┤
|
||||||
|
// │ A ┃ 0 │ 0 │ 0 ┃ 0 │ 0 │
|
||||||
|
// │ B ┃ 0 │ 0 │ 1 ┃ 1 │ 0 │
|
||||||
|
// │ C ┃ 0 │ 1 │ 0 ┃ 1 │ 1 │
|
||||||
|
// │ D ┃ 0 │ 1 │ 1 ┃ 1 │ 0 │
|
||||||
|
// │ E ┃ 1 │ 0 │ 0 ┃ 0 │ 1 │
|
||||||
|
// │ F ┃ 1 │ 0 │ 1 ┃ 1 │ 1 │
|
||||||
|
// │ G ┃ 1 │ 1 │ 0 ┃ 0 │ 1 │
|
||||||
|
// │ H ┃ 1 │ 1 │ 1 ┃ 0 │ 0 │
|
||||||
|
// └─────┸────┴────┴────┸────┴────┘
|
||||||
|
//
|
||||||
|
// Lookup table constant:
|
||||||
|
// H G F E D C B A
|
||||||
|
// 00 01 11 01 10 11 10 00 = 0x1DB8
|
||||||
|
|
||||||
|
let sign0 = asuint(y0) >> 31;
|
||||||
|
let sign1 = asuint(y1) >> 31;
|
||||||
|
let sign2 = asuint(y2) >> 31;
|
||||||
|
|
||||||
|
let class = (sign0 << 0) | (sign1 << 1) | (sign2 << 2);
|
||||||
|
let shift = 2 * class;
|
||||||
|
|
||||||
|
return (0x1DB8 >> shift) & 0b11U;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find intersections between a quadratic Bézier curve and `y = 0` line by
|
||||||
|
// solving a quadratic equation.
|
||||||
|
//
|
||||||
|
// Returns the X coordinates at witch the intersections occur for roots t1 and
|
||||||
|
// t2. Imaginary solutions to the quadratic equation will return double root at
|
||||||
|
// the global minimum.
|
||||||
|
float2 raycastQuadCurveHorizontally(float2 cp0, float2 cp1, float2 cp2)
|
||||||
|
{
|
||||||
|
static const float EPSILON = 1.0 / 65536.0;
|
||||||
|
|
||||||
|
// When solving for y = 0, we get a quadratic polynomial given by:
|
||||||
|
//
|
||||||
|
// at² - 2bt + c
|
||||||
|
//
|
||||||
|
// where:
|
||||||
|
// * a = cp0.y - 2 * cp1.y + cp2.y
|
||||||
|
// * b = cp0.y - cp1.y
|
||||||
|
// * c = cp0.y
|
||||||
|
//
|
||||||
|
// Let Δ = b² - ac
|
||||||
|
// Then:
|
||||||
|
//
|
||||||
|
// t1 = (b - sqrt(Δ)) / a
|
||||||
|
// t2 = (b + sqrt(Δ)) / a
|
||||||
|
|
||||||
|
let a = cp0 - 2.0 * cp1 + cp2;
|
||||||
|
let b = cp0 - cp1;
|
||||||
|
let c = cp0;
|
||||||
|
|
||||||
|
let Δ = sqrt(max(b.y * b.y - a.y * c.y, 0.0));
|
||||||
|
var t = (b.y + float2(-Δ, Δ)) / a.y;
|
||||||
|
|
||||||
|
// If nearly linear, solve -2bt + c = 0 directly
|
||||||
|
if (abs(a.y) < EPSILON) {
|
||||||
|
t = float2(0.5 * c.y / b.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (a.x * t - 2.0 * b.x) * t + c.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find intersections between a quadratic Bézier curve and `x = 0` line by
|
||||||
|
// solving a quadratic equation.
|
||||||
|
//
|
||||||
|
// Returns the Y coordinates at witch the intersections occur for roots t1 and
|
||||||
|
// t2. Imaginary solutions to the quadratic equation will return double root at
|
||||||
|
// the global minimum.
|
||||||
|
float2 raycastQuadCurveVertically(float2 cp0, float2 cp1, float2 cp2)
|
||||||
|
{
|
||||||
|
static const float EPSILON = 1.0 / 65536.0;
|
||||||
|
|
||||||
|
let a = cp0 - 2.0 * cp1 + cp2;
|
||||||
|
let b = cp0 - cp1;
|
||||||
|
let c = cp0;
|
||||||
|
|
||||||
|
let Δ = sqrt(max(b.x * b.x - a.x * c.x, 0.0));
|
||||||
|
var t = (b.x + float2(-Δ, Δ)) / a.x;
|
||||||
|
|
||||||
|
if (abs(a.x) < EPSILON) {
|
||||||
|
t = float2(0.5 * c.x / b.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (a.y * t - 2.0 * b.y) * t + c.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
float calculateCoverage(float xCoverage, float yCoverage, float xWeight, float yWeight) {
|
||||||
|
static const float EPSILON = 1.0 / 65536.0;
|
||||||
|
|
||||||
|
var coverage = max(
|
||||||
|
abs(xCoverage * xWeight + yCoverage * yWeight) / max(xWeight + yWeight, EPSILON),
|
||||||
|
min(abs(xCoverage), abs(yCoverage)),
|
||||||
|
);
|
||||||
|
// non-zero fill rule
|
||||||
|
coverage = saturate(coverage);
|
||||||
|
|
||||||
|
return coverage;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("vertex")]
|
||||||
|
Varyings vertexMain(uint vertexID : SV_VulkanVertexID, uint instanceID : SV_VulkanInstanceID) {
|
||||||
|
Varyings out;
|
||||||
|
|
||||||
|
let vertex = VERTICES[vertexID];
|
||||||
|
let instance = _Instances[instanceID];
|
||||||
|
let glyph = _GlyphData[instance.font][instance.glyph];
|
||||||
|
let normal = 2.0 * vertex - 1.0;
|
||||||
|
|
||||||
|
let sizePX = instance.fontSizePX * glyph.sizeEM;
|
||||||
|
|
||||||
|
let positionSSPX = vertex * float2(sizePX.x, -sizePX.y) + instance.bottomLeftSSPX;
|
||||||
|
let dilatedPositionSSPX = positionSSPX + 0.5 * normal;
|
||||||
|
let dilatedPositionCS = float4(mul(_Global.matrixSSPXtoCS, float3(positionSSPX, 1.0)), 0.0, 1.0);
|
||||||
|
|
||||||
|
let texCoordEM = vertex * glyph.sizeEM + glyph.bottomLeftEM;
|
||||||
|
let dilatedTexCoordEM = texCoordEM + (0.5 / instance.fontSizePX) * normal;
|
||||||
|
|
||||||
|
out.positionCS = dilatedPositionCS;
|
||||||
|
out.instance = instanceID;
|
||||||
|
out.texCoordEM = dilatedTexCoordEM;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("fragment")]
|
||||||
|
float4 fragmentMain(Varyings frag) : SV_Target {
|
||||||
|
let instance = _Instances[frag.instance];
|
||||||
|
let glyph = _GlyphData[instance.font][instance.glyph];
|
||||||
|
let bandData = _BandData[instance.font];
|
||||||
|
let controlPoints = _ControlPoints[instance.font];
|
||||||
|
|
||||||
|
let emPerPX = fwidth(frag.texCoordEM);
|
||||||
|
let pxPerEM = 1.0 / emPerPX;
|
||||||
|
|
||||||
|
let bandF = float2(glyph.bandCount) * (frag.texCoordEM - glyph.bottomLeftEM) / glyph.sizeEM;
|
||||||
|
let band = uint2(clamp(bandF, float2(0, 0), float2(glyph.bandCount - 1U)));
|
||||||
|
|
||||||
|
// --- HORIZONTAL BAND -----------------------------------------------------
|
||||||
|
|
||||||
|
var xCoverage = 0.0;
|
||||||
|
var xWeight = 0.0;
|
||||||
|
|
||||||
|
let horizontalBandPtr = glyph.bandPtr.y + band.y;
|
||||||
|
let horizontalBandData = bandData[horizontalBandPtr];
|
||||||
|
let horizontalBandCurvePtr = horizontalBandPtr + (horizontalBandData >> 16);
|
||||||
|
let horizontalBandCurveCount = horizontalBandData & 0xFFFF;
|
||||||
|
|
||||||
|
for (uint horizontalBandCurveID = 0; horizontalBandCurveID < horizontalBandCurveCount; horizontalBandCurveID++) {
|
||||||
|
let cpPtr = bandData[horizontalBandCurvePtr + horizontalBandCurveID];
|
||||||
|
let cp0 = float2(controlPoints[cpPtr + 0]) - frag.texCoordEM;
|
||||||
|
let cp1 = float2(controlPoints[cpPtr + 1]) - frag.texCoordEM;
|
||||||
|
let cp2 = float2(controlPoints[cpPtr + 2]) - frag.texCoordEM;
|
||||||
|
|
||||||
|
if (max(max(cp0.x, cp1.x), cp2.x) * pxPerEM.x < -0.5) break; // XXX
|
||||||
|
|
||||||
|
let rootFlags = classifyQuadCurve(cp0.y, cp1.y, cp2.y);
|
||||||
|
if (rootFlags != 0U) {
|
||||||
|
|
||||||
|
let result = raycastQuadCurveHorizontally(cp0, cp1, cp2) * pxPerEM.x;
|
||||||
|
|
||||||
|
// consider t1 at X position result.x
|
||||||
|
if ((rootFlags & 0b01U) != 0U) {
|
||||||
|
xCoverage += saturate(result.x + 0.5);
|
||||||
|
xWeight = max(xWeight, saturate(1.0 - abs(result.x) * 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// consider t2 at X position result.y
|
||||||
|
if ((rootFlags & 0b10U) != 0U) {
|
||||||
|
xCoverage -= saturate(result.y + 0.5);
|
||||||
|
xWeight = max(xWeight, saturate(1.0 - abs(result.y) * 2.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- VERTICAL BAND -------------------------------------------------------
|
||||||
|
|
||||||
|
var yCoverage = 0.0;
|
||||||
|
var yWeight = 0.0;
|
||||||
|
|
||||||
|
let verticalBandPtr = glyph.bandPtr.x + band.x;
|
||||||
|
let verticalBandData = bandData[verticalBandPtr];
|
||||||
|
let verticalBandCurvePtr = verticalBandPtr + (verticalBandData >> 16);
|
||||||
|
let verticalBandCurveCount = verticalBandData & 0xFFFF;
|
||||||
|
|
||||||
|
for (uint verticalBandCurveID = 0; verticalBandCurveID < verticalBandCurveCount; verticalBandCurveID++) {
|
||||||
|
let cpPtr = bandData[verticalBandCurvePtr + verticalBandCurveID];
|
||||||
|
let cp0 = float2(controlPoints[cpPtr + 0]) - frag.texCoordEM;
|
||||||
|
let cp1 = float2(controlPoints[cpPtr + 1]) - frag.texCoordEM;
|
||||||
|
let cp2 = float2(controlPoints[cpPtr + 2]) - frag.texCoordEM;
|
||||||
|
|
||||||
|
if (max(max(cp0.y, cp1.y), cp2.y) * pxPerEM.y < -0.5) break; // XXX
|
||||||
|
|
||||||
|
let rootFlags = classifyQuadCurve(cp0.x, cp1.x, cp2.x);
|
||||||
|
if (rootFlags != 0U) {
|
||||||
|
|
||||||
|
let result = raycastQuadCurveVertically(cp0, cp1, cp2) * pxPerEM.y;
|
||||||
|
|
||||||
|
// consider t1 at Y position result.x
|
||||||
|
if ((rootFlags & 0b01U) != 0U)
|
||||||
|
{
|
||||||
|
yCoverage -= saturate(result.x + 0.5);
|
||||||
|
yWeight = max(yWeight, saturate(1.0 - abs(result.x) * 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// consider t2 at Y position result.y
|
||||||
|
if ((rootFlags & 0b10U) != 0U)
|
||||||
|
{
|
||||||
|
yCoverage += saturate(result.y + 0.5);
|
||||||
|
yWeight = max(yWeight, saturate(1.0 - abs(result.y) * 2.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- RESOLVE -------------------------------------------------------------
|
||||||
|
|
||||||
|
let coverage = calculateCoverage(xCoverage, yCoverage, xWeight, yWeight) * instance.color.a;
|
||||||
|
return float4(
|
||||||
|
coverage * instance.color.rgb,
|
||||||
|
coverage,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#version 460
|
|
||||||
#extension GL_EXT_nonuniform_qualifier : require
|
|
||||||
#extension GL_EXT_scalar_block_layout : require
|
|
||||||
#extension GL_EXT_shader_16bit_storage : require
|
|
||||||
|
|
||||||
layout(location = 0) in vec4 positionOS_normalOS;
|
|
||||||
layout(location = 1) in vec4 texCoordEM_band_flags;
|
|
||||||
layout(location = 2) in vec4 jacobian;
|
|
||||||
layout(location = 3) in vec4 scale_offset;
|
|
||||||
layout(location = 4) in vec4 color;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
layout(set = 0, binding = 0, scalar) uniform GlobalUniforms {
|
|
||||||
mat4 matrixWStoVS;
|
|
||||||
mat4 matrixVStoCS;
|
|
||||||
mat3x2 matrixSSPXtoCS;
|
|
||||||
vec3 ambientLight;
|
|
||||||
} _Global;
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
struct Box {
|
|
||||||
vec4 backgroundColor;
|
|
||||||
vec4 borderColor;
|
|
||||||
vec2 positionSSPX;
|
|
||||||
vec2 sizePX;
|
|
||||||
float borderWidthPX;
|
|
||||||
float borderRadiusPX;
|
|
||||||
};
|
|
||||||
|
|
||||||
#include "global_uniforms.glsl"
|
|
||||||
|
|
||||||
layout(set = 0, binding = 1, scalar) readonly buffer Boxes {
|
|
||||||
Box _Boxes[];
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
struct Image {
|
|
||||||
vec4 tint;
|
|
||||||
vec2 positionSSPX;
|
|
||||||
vec2 sizePX;
|
|
||||||
vec2 uvMin;
|
|
||||||
vec2 uvMax;
|
|
||||||
uint16_t textureId;
|
|
||||||
};
|
|
||||||
|
|
||||||
#include "global_uniforms.glsl"
|
|
||||||
|
|
||||||
layout(set = 0, binding = 1, scalar) readonly buffer Images {
|
|
||||||
Image _Images[];
|
|
||||||
};
|
|
||||||
|
|
||||||
layout(set = 0, binding = 2) uniform sampler _Sampler;
|
|
||||||
layout(set = 0, binding = 3) uniform texture2D _Textures[];
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
// --- SET 0 --- GLOBAL --------------------------------------------------------
|
|
||||||
|
|
||||||
struct PointLight {
|
|
||||||
vec3 positionWS;
|
|
||||||
vec3 color;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DirectionalLight {
|
|
||||||
vec3 directionWS;
|
|
||||||
vec3 color;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Material {
|
|
||||||
vec3 baseColor;
|
|
||||||
vec3 emissive;
|
|
||||||
float ior;
|
|
||||||
float metallic;
|
|
||||||
float normalScale;
|
|
||||||
float occlusionTextureStrength;
|
|
||||||
float roughness;
|
|
||||||
|
|
||||||
uint16_t baseColorTexture;
|
|
||||||
uint16_t emissiveTexture;
|
|
||||||
uint16_t normalTexture;
|
|
||||||
uint16_t occlusionRoughnessMetallicTexture;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ObjectUniforms {
|
|
||||||
mat4 matrixOStoWS;
|
|
||||||
mat4 matrixOStoWSNormal;
|
|
||||||
|
|
||||||
uint16_t material;
|
|
||||||
};
|
|
||||||
|
|
||||||
#include "global_uniforms.glsl"
|
|
||||||
|
|
||||||
layout(set = 0, binding = 1, scalar) readonly buffer PointLights {
|
|
||||||
uint count;
|
|
||||||
PointLight lights[];
|
|
||||||
} _PointLights;
|
|
||||||
|
|
||||||
layout(set = 0, binding = 2, scalar) readonly buffer DirectionalLights {
|
|
||||||
uint count;
|
|
||||||
DirectionalLight lights[];
|
|
||||||
} _DirectionalLights;
|
|
||||||
|
|
||||||
layout(set = 0, binding = 3, scalar) readonly buffer Materials {
|
|
||||||
Material _Materials[];
|
|
||||||
};
|
|
||||||
|
|
||||||
layout(set = 0, binding = 4) uniform sampler _Sampler;
|
|
||||||
layout(set = 0, binding = 5) uniform texture2D _Textures[];
|
|
||||||
|
|
||||||
// --- SET 1 --- PER BATCH -----------------------------------------------------
|
|
||||||
|
|
||||||
layout(set = 1, binding = 0, scalar) readonly buffer ObjectsUniforms {
|
|
||||||
ObjectUniforms _Object[];
|
|
||||||
};
|
|
||||||
77
assets/shaders/includes/math.slang
Normal file
77
assets/shaders/includes/math.slang
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import "structs";
|
||||||
|
|
||||||
|
static const float INV_PI = 0.31830987;
|
||||||
|
static const float HALF_INV_PI = 0.15915494;
|
||||||
|
|
||||||
|
static const float3 F90 = float3(1.0);
|
||||||
|
|
||||||
|
float4 texture2DAA(Texture2D texture, SamplerState sampler, float2 texCoord) {
|
||||||
|
uint width, height;
|
||||||
|
texture.GetDimensions(width, height);
|
||||||
|
let size = float2(width, height);
|
||||||
|
|
||||||
|
var texCoordPX = texCoord * size;
|
||||||
|
let seam = floor(texCoordPX + 0.5);
|
||||||
|
|
||||||
|
texCoordPX = (texCoordPX - seam) / fwidth(texCoordPX) + seam;
|
||||||
|
texCoordPX = clamp(texCoordPX, seam - 0.5, seam + 0.5);
|
||||||
|
|
||||||
|
texCoord = texCoordPX / size;
|
||||||
|
return texture.Sample(sampler, texCoord);
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 fresnelSchlick(float dotVH, float3 f0) {
|
||||||
|
return lerp(f0, F90, pow(1.0 - dotVH, 5.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
float visibilityGGX(float dotNL, float dotNV, float alpha) {
|
||||||
|
let alphaSquared = alpha * alpha;
|
||||||
|
|
||||||
|
let vGGX = dotNL * sqrt(dotNV * dotNV * (1.0 - alphaSquared) + alphaSquared);
|
||||||
|
let lGGX = dotNV * sqrt(dotNL * dotNL * (1.0 - alphaSquared) + alphaSquared);
|
||||||
|
let GGX = vGGX + lGGX;
|
||||||
|
return select(GGX > 0.0, 0.5 / GGX, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float distributionGGX(float dotNH, float alpha) {
|
||||||
|
let alphaSquared = alpha * alpha;
|
||||||
|
let tmp = dotNH * dotNH * (alphaSquared - 1.0) + 1.0;
|
||||||
|
return alphaSquared * INV_PI / (tmp * tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 lightOutgoingRadiance(
|
||||||
|
float3 viewDirectionVS, float3 normalVS, float dotNV,
|
||||||
|
Surface surf, IncomingLight light,
|
||||||
|
) {
|
||||||
|
let halfVectorVS = normalize(light.directionVS + viewDirectionVS);
|
||||||
|
let dotVH = saturate(dot(viewDirectionVS, halfVectorVS));
|
||||||
|
let dotNH = saturate(dot(normalVS, halfVectorVS));
|
||||||
|
let dotNL = saturate(dot(normalVS, light.directionVS));
|
||||||
|
|
||||||
|
let fresnel = fresnelSchlick(dotVH, surf.f0);
|
||||||
|
let visibility = visibilityGGX(dotNL, dotNV, surf.alpha);
|
||||||
|
let distribution = distributionGGX(dotNH, surf.alpha);
|
||||||
|
|
||||||
|
let scatteredFactor = (1.0 - fresnel) * (1.0 - surf.metallic) * surf.baseColor * INV_PI;
|
||||||
|
let reflectedFactor = fresnel * visibility * distribution;
|
||||||
|
|
||||||
|
return (scatteredFactor + reflectedFactor) * light.radiance * dotNL;
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 toneMapAcesNarkowicz(float3 color) {
|
||||||
|
static const float A = 2.51;
|
||||||
|
static const float B = 0.03;
|
||||||
|
static const float C = 2.43;
|
||||||
|
static const float D = 0.59;
|
||||||
|
static const float E = 0.14;
|
||||||
|
return saturate((color * (A * color + B)) / (color * (C * color + D) + E));
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 linearToSrgbColor(float3 color) {
|
||||||
|
return pow(color, 1.0 / 2.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
float boxSDF(float2 p, float2 center, float2 halfExtents, float borderRadius) {
|
||||||
|
let q = abs(p - center) - halfExtents + borderRadius;
|
||||||
|
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - borderRadius;
|
||||||
|
}
|
||||||
83
assets/shaders/includes/structs.slang
Normal file
83
assets/shaders/includes/structs.slang
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
struct GlobalUniforms {
|
||||||
|
float3x4 matrixWStoVS;
|
||||||
|
float4x4 matrixVStoCS;
|
||||||
|
float2x3 matrixSSPXtoCS;
|
||||||
|
|
||||||
|
uint pointLightCount;
|
||||||
|
uint directionalLightCount;
|
||||||
|
float3 ambientLight;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PointLight {
|
||||||
|
float3 positionWS;
|
||||||
|
float3 color;
|
||||||
|
|
||||||
|
IncomingLight getIncoming(float3x4 matrixWStoVS, float3 positionVS) {
|
||||||
|
IncomingLight light;
|
||||||
|
|
||||||
|
let lightPositionVS = mul(matrixWStoVS, float4(positionWS, 1.0));
|
||||||
|
let lightDirectionVS = normalize(lightPositionVS - positionVS);
|
||||||
|
let lightDistance = distance(positionVS, lightPositionVS);
|
||||||
|
let lightAttenuation = 1.0 / (lightDistance * lightDistance);
|
||||||
|
let incomingRadiance = color * lightAttenuation;
|
||||||
|
|
||||||
|
light.radiance = incomingRadiance;
|
||||||
|
light.directionVS = lightDirectionVS;
|
||||||
|
return light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DirectionalLight {
|
||||||
|
float3 directionWS;
|
||||||
|
float3 color;
|
||||||
|
|
||||||
|
IncomingLight getIncoming(float3x4 matrixWStoVS) {
|
||||||
|
IncomingLight light;
|
||||||
|
|
||||||
|
light.radiance = color;
|
||||||
|
light.directionVS = normalize(mul(matrixWStoVS, float4(-directionWS, 0.0)));
|
||||||
|
return light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Material {
|
||||||
|
float3 baseColor;
|
||||||
|
float3 emissive;
|
||||||
|
float ior;
|
||||||
|
float metallic;
|
||||||
|
float normalScale;
|
||||||
|
float occlusionTextureStrength;
|
||||||
|
float roughness;
|
||||||
|
|
||||||
|
uint16_t baseColorTexture;
|
||||||
|
uint16_t emissiveTexture;
|
||||||
|
uint16_t normalTexture;
|
||||||
|
uint16_t occlusionRoughnessMetallicTexture;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ObjectUniforms {
|
||||||
|
// Limited to a composition of translation and rotation
|
||||||
|
float3x4 matrixOStoWS;
|
||||||
|
float2x3 texCoordTransform;
|
||||||
|
|
||||||
|
uint16_t material;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Surface {
|
||||||
|
float3 baseColor;
|
||||||
|
float alpha;
|
||||||
|
float metallic;
|
||||||
|
float3 f0;
|
||||||
|
|
||||||
|
__init(float3 baseColor, float alpha, float metallic, float3 f0) {
|
||||||
|
this.baseColor = baseColor;
|
||||||
|
this.alpha = alpha;
|
||||||
|
this.metallic = metallic;
|
||||||
|
this.f0 = f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IncomingLight {
|
||||||
|
float3 radiance;
|
||||||
|
float3 directionVS;
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
vec3 toneMapAcesNarkowicz(vec3 color) {
|
|
||||||
const float A = 2.51;
|
|
||||||
const float B = 0.03;
|
|
||||||
const float C = 2.43;
|
|
||||||
const float D = 0.59;
|
|
||||||
const float E = 0.14;
|
|
||||||
return clamp((color * (A * color + B)) / (color * (C * color + D) + E), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
#version 460
|
|
||||||
#extension GL_EXT_nonuniform_qualifier : require
|
|
||||||
#extension GL_EXT_scalar_block_layout : require
|
|
||||||
#extension GL_EXT_shader_16bit_storage : require
|
|
||||||
|
|
||||||
in Varyings {
|
|
||||||
layout(location = 0) flat uint instance;
|
|
||||||
layout(location = 1) vec3 positionVS;
|
|
||||||
layout(location = 2) vec2 texCoord;
|
|
||||||
layout(location = 3) vec3 normalVS;
|
|
||||||
layout(location = 4) vec3 tangentVS;
|
|
||||||
layout(location = 5) vec3 bitangentVS;
|
|
||||||
} var;
|
|
||||||
|
|
||||||
#include "includes/main_common.glsl"
|
|
||||||
#include "includes/tone_mapping.glsl"
|
|
||||||
|
|
||||||
layout(location = 0) out vec4 fragColor;
|
|
||||||
|
|
||||||
const float INV_PI = 0.31830987;
|
|
||||||
const float IOR = 1.45;
|
|
||||||
const vec3 F90 = vec3(1.0);
|
|
||||||
|
|
||||||
vec3 fresnelSchlick(float dotVH, vec3 f0) {
|
|
||||||
return mix(f0, F90, pow(1.0 - dotVH, 5.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
float visibilityGGX(float dotNL, float dotNV, float alpha) {
|
|
||||||
float alphaSquared = alpha * alpha;
|
|
||||||
|
|
||||||
float vGGX = dotNL * sqrt(dotNV * dotNV * (1.0 - alphaSquared) + alphaSquared);
|
|
||||||
float lGGX = dotNV * sqrt(dotNL * dotNL * (1.0 - alphaSquared) + alphaSquared);
|
|
||||||
float GGX = vGGX + lGGX;
|
|
||||||
return mix(0.0, 0.5 / GGX, GGX > 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float distributionGGX(float dotNH, float alpha) {
|
|
||||||
float alphaSquared = alpha * alpha;
|
|
||||||
float tmp = dotNH * dotNH * (alphaSquared - 1.0) + 1.0;
|
|
||||||
return alphaSquared * INV_PI / (tmp * tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 lightOutgoingRadiance(
|
|
||||||
vec3 viewDirectionVS, vec3 normalVS, float dotNV,
|
|
||||||
vec3 baseColor, float alpha, float metallic, vec3 f0,
|
|
||||||
vec3 incomingRadiance, vec3 lightDirectionVS
|
|
||||||
) {
|
|
||||||
vec3 halfVectorVS = normalize(lightDirectionVS + viewDirectionVS);
|
|
||||||
float dotVH = clamp(dot(viewDirectionVS, halfVectorVS), 0.0, 1.0);
|
|
||||||
float dotNH = clamp(dot(normalVS, halfVectorVS), 0.0, 1.0);
|
|
||||||
float dotNL = clamp(dot(normalVS, lightDirectionVS), 0.0, 1.0);
|
|
||||||
|
|
||||||
vec3 fresnel = fresnelSchlick(dotVH, f0);
|
|
||||||
float visibility = visibilityGGX(dotNL, dotNV, alpha);
|
|
||||||
float distribution = distributionGGX(dotNH, alpha);
|
|
||||||
|
|
||||||
vec3 scatteredFactor = (1.0 - fresnel) * (1.0 - metallic) * baseColor * INV_PI;
|
|
||||||
vec3 reflectedFactor = fresnel * visibility * distribution;
|
|
||||||
|
|
||||||
return (scatteredFactor + reflectedFactor) * incomingRadiance * dotNL;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 texture2DAA(texture2D tex, vec2 texCoord) {
|
|
||||||
vec2 size = vec2(textureSize(sampler2D(tex, _Sampler), 0).xy);
|
|
||||||
vec2 texCoordPX = texCoord * size;
|
|
||||||
vec2 seam = floor(texCoordPX + vec2(0.5));
|
|
||||||
|
|
||||||
texCoordPX = (texCoordPX - seam) / fwidth(texCoordPX) + seam;
|
|
||||||
texCoordPX = clamp(texCoordPX, seam - 0.5, seam + 0.5);
|
|
||||||
|
|
||||||
texCoord = texCoordPX / size;
|
|
||||||
return texture(sampler2D(tex, _Sampler), texCoord);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define OBJECT _Object[var.instance]
|
|
||||||
#define MATERIAL _Materials[uint(OBJECT.material)]
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 baseColorTexel = texture2DAA(_Textures[uint(MATERIAL.baseColorTexture)], var.texCoord);
|
|
||||||
if (baseColorTexel.a < 0.5) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 occlusionRoughnessMetallicTexel = texture2DAA(_Textures[uint(MATERIAL.occlusionRoughnessMetallicTexture)], var.texCoord);
|
|
||||||
vec4 normalTexel = texture2DAA(_Textures[uint(MATERIAL.normalTexture)], var.texCoord);
|
|
||||||
vec4 emissiveTexel = texture2DAA(_Textures[uint(MATERIAL.emissiveTexture)], var.texCoord);
|
|
||||||
|
|
||||||
vec3 baseColor = MATERIAL.baseColor * baseColorTexel.rgb;
|
|
||||||
float occlusion = 1.0 + MATERIAL.occlusionTextureStrength * (occlusionRoughnessMetallicTexel.r - 1.0);
|
|
||||||
float roughness = MATERIAL.roughness * occlusionRoughnessMetallicTexel.g;
|
|
||||||
float metallic = MATERIAL.metallic * occlusionRoughnessMetallicTexel.b;
|
|
||||||
vec3 emissive = MATERIAL.emissive * emissiveTexel.rgb;
|
|
||||||
float ior = MATERIAL.ior;
|
|
||||||
|
|
||||||
vec3 tangentVS = normalize(var.tangentVS);
|
|
||||||
vec3 bitangentVS = normalize(var.bitangentVS);
|
|
||||||
mat3 matrixTStoVS = mat3(tangentVS, bitangentVS, var.normalVS);
|
|
||||||
vec3 normalTS = normalTexel.xyz;
|
|
||||||
vec3 normalVS = normalize(matrixTStoVS * normalTS);
|
|
||||||
|
|
||||||
vec3 positionVS = var.positionVS;
|
|
||||||
vec3 viewDirectionVS = normalize(-positionVS);
|
|
||||||
float dotNV = clamp(dot(normalVS, viewDirectionVS), 0.0, 1.0);
|
|
||||||
float alpha = roughness * roughness;
|
|
||||||
|
|
||||||
vec3 f0 = vec3(pow((ior - 1.0) / (ior + 1.0), 2.0));
|
|
||||||
f0 = mix(f0, baseColor, metallic);
|
|
||||||
|
|
||||||
vec3 outgoingRadiance = vec3(0.0);
|
|
||||||
|
|
||||||
for (uint i = 0; i < _PointLights.count; i++) {
|
|
||||||
PointLight light = _PointLights.lights[i];
|
|
||||||
|
|
||||||
vec3 lightPositionVS = (_Global.matrixWStoVS * vec4(light.positionWS, 1.0)).xyz;
|
|
||||||
vec3 lightDirectionVS = normalize(lightPositionVS - positionVS);
|
|
||||||
float lightDistance = distance(positionVS, lightPositionVS);
|
|
||||||
float lightAttenuation = 1.0 / (lightDistance * lightDistance);
|
|
||||||
vec3 incomingRadiance = light.color * lightAttenuation;
|
|
||||||
|
|
||||||
outgoingRadiance += lightOutgoingRadiance(
|
|
||||||
viewDirectionVS, normalVS, dotNV,
|
|
||||||
baseColor, alpha, metallic, f0,
|
|
||||||
incomingRadiance, lightDirectionVS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < _DirectionalLights.count; i++) {
|
|
||||||
DirectionalLight light = _DirectionalLights.lights[i];
|
|
||||||
|
|
||||||
vec3 lightDirectionVS = normalize((_Global.matrixWStoVS * vec4(-light.directionWS, 0.0)).xyz);
|
|
||||||
vec3 incomingRadiance = light.color;
|
|
||||||
|
|
||||||
outgoingRadiance += lightOutgoingRadiance(
|
|
||||||
viewDirectionVS, normalVS, dotNV,
|
|
||||||
baseColor, alpha, metallic, f0,
|
|
||||||
incomingRadiance, lightDirectionVS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
outgoingRadiance += _Global.ambientLight * baseColor * occlusion;
|
|
||||||
|
|
||||||
vec3 toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance);
|
|
||||||
vec3 toneMappedSrgbColor = pow(toneMappedLinearColor, vec3(1.0 / 2.2));
|
|
||||||
|
|
||||||
fragColor = vec4(toneMappedSrgbColor, 1.0);
|
|
||||||
}
|
|
||||||
124
assets/shaders/main.slang
Normal file
124
assets/shaders/main.slang
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import "includes/math";
|
||||||
|
import "includes/structs";
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
[[vk::location(0)]] float3 positionOS;
|
||||||
|
[[vk::location(1)]] float2 texCoord;
|
||||||
|
[[vk::location(2)]] float3 normalOS;
|
||||||
|
[[vk::location(3)]] float4 tangentOS;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Varyings {
|
||||||
|
float4 positionCS : SV_Position;
|
||||||
|
nointerpolation uint instance;
|
||||||
|
float3 positionVS;
|
||||||
|
float2 texCoord;
|
||||||
|
float3 normalVS;
|
||||||
|
float3 tangentVS;
|
||||||
|
float3 bitangentVS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[vk::binding(0, 0)]] ConstantBuffer<GlobalUniforms> _Global;
|
||||||
|
[[vk::binding(1, 0)]] StructuredBuffer<PointLight> _PointLights;
|
||||||
|
[[vk::binding(2, 0)]] StructuredBuffer<DirectionalLight> _DirectionalLights;
|
||||||
|
[[vk::binding(3, 0)]] StructuredBuffer<Material> _Materials;
|
||||||
|
[[vk::binding(4, 0)]] SamplerState _Sampler;
|
||||||
|
[[vk::binding(5, 0)]] Texture2D _Textures[];
|
||||||
|
|
||||||
|
[[vk::binding(0, 1)]] StructuredBuffer<ObjectUniforms> _Objects;
|
||||||
|
|
||||||
|
[shader("vertex")]
|
||||||
|
Varyings vertexMain(Vertex vert, uint instanceID : SV_VulkanInstanceID) {
|
||||||
|
Varyings out;
|
||||||
|
|
||||||
|
let object = _Objects[instanceID];
|
||||||
|
|
||||||
|
let positionWS = mul(object.matrixOStoWS, float4(vert.positionOS, 1.0));
|
||||||
|
let positionVS = mul(_Global.matrixWStoVS, float4(positionWS, 1.0));
|
||||||
|
let positionCS = mul(_Global.matrixVStoCS, float4(positionVS, 1.0));
|
||||||
|
|
||||||
|
let texCoord = mul(object.texCoordTransform, float3(vert.texCoord, 1.0));
|
||||||
|
|
||||||
|
let normalWS = normalize(mul(object.matrixOStoWS, float4(vert.normalOS, 0.0)));
|
||||||
|
let normalVS = normalize(mul(_Global.matrixWStoVS, float4(normalWS, 0.0)));
|
||||||
|
|
||||||
|
let tangentWS = normalize(mul(object.matrixOStoWS, float4(vert.tangentOS.xyz, 0.0)));
|
||||||
|
let tangentVS = normalize(mul(_Global.matrixWStoVS, float4(tangentWS, 0.0)));
|
||||||
|
|
||||||
|
let bitangentVS = vert.tangentOS.w * normalize(cross(normalVS, tangentVS));
|
||||||
|
|
||||||
|
out.positionCS = positionCS;
|
||||||
|
out.instance = instanceID;
|
||||||
|
out.positionVS = positionVS;
|
||||||
|
out.texCoord = texCoord;
|
||||||
|
out.normalVS = normalVS;
|
||||||
|
out.tangentVS = tangentVS;
|
||||||
|
out.bitangentVS = bitangentVS;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("fragment")]
|
||||||
|
float4 fragmentMain(Varyings frag) : SV_Target {
|
||||||
|
let material = _Materials[_Objects[frag.instance].material];
|
||||||
|
|
||||||
|
let baseColorTexel = texture2DAA(_Textures[material.baseColorTexture], _Sampler, frag.texCoord);
|
||||||
|
if (baseColorTexel.a < 0.5) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
let occlusionRoughnessMetallicTexel = texture2DAA(_Textures[material.occlusionRoughnessMetallicTexture], _Sampler, frag.texCoord);
|
||||||
|
let normalTexel = texture2DAA(_Textures[material.normalTexture], _Sampler, frag.texCoord);
|
||||||
|
let emissiveTexel = texture2DAA(_Textures[material.emissiveTexture], _Sampler, frag.texCoord);
|
||||||
|
|
||||||
|
let baseColor = material.baseColor * baseColorTexel.rgb;
|
||||||
|
let occlusion = 1.0 + material.occlusionTextureStrength * (occlusionRoughnessMetallicTexel.r - 1.0);
|
||||||
|
let roughness = material.roughness * occlusionRoughnessMetallicTexel.g;
|
||||||
|
let metallic = material.metallic * occlusionRoughnessMetallicTexel.b;
|
||||||
|
let emissive = material.emissive * emissiveTexel.rgb;
|
||||||
|
let ior = material.ior;
|
||||||
|
|
||||||
|
let tangentVS = normalize(frag.tangentVS);
|
||||||
|
let bitangentVS = normalize(frag.bitangentVS);
|
||||||
|
let matrixTStoVS = transpose(float3x3(tangentVS, bitangentVS, frag.normalVS));
|
||||||
|
let normalTS = normalTexel.xyz;
|
||||||
|
let normalVS = normalize(mul(matrixTStoVS, normalTS));
|
||||||
|
|
||||||
|
let positionVS = frag.positionVS;
|
||||||
|
let viewDirectionVS = normalize(-positionVS);
|
||||||
|
let dotNV = saturate(dot(normalVS, viewDirectionVS));
|
||||||
|
let alpha = roughness * roughness;
|
||||||
|
|
||||||
|
var f0 = float3(pow((ior - 1.0) / (ior + 1.0), 2.0));
|
||||||
|
f0 = lerp(f0, baseColor, metallic);
|
||||||
|
|
||||||
|
let surface = Surface(baseColor, alpha, metallic, f0);
|
||||||
|
|
||||||
|
var outgoingRadiance = float3(0.0);
|
||||||
|
|
||||||
|
let pointLightCount = _Global.pointLightCount;
|
||||||
|
for (uint i = 0; i < pointLightCount; i++) {
|
||||||
|
let light = _PointLights[i].getIncoming(_Global.matrixWStoVS, positionVS);
|
||||||
|
|
||||||
|
outgoingRadiance += lightOutgoingRadiance(
|
||||||
|
viewDirectionVS, normalVS, dotNV,
|
||||||
|
surface, light,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let directionalLightCount = _Global.directionalLightCount;
|
||||||
|
for (uint i = 0; i < directionalLightCount; i++) {
|
||||||
|
let light = _DirectionalLights[i].getIncoming(_Global.matrixWStoVS);
|
||||||
|
|
||||||
|
outgoingRadiance += lightOutgoingRadiance(
|
||||||
|
viewDirectionVS, normalVS, dotNV,
|
||||||
|
surface, light,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
outgoingRadiance += _Global.ambientLight * baseColor * occlusion;
|
||||||
|
|
||||||
|
let toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance);
|
||||||
|
let toneMappedSrgbColor = linearToSrgbColor(toneMappedLinearColor);
|
||||||
|
|
||||||
|
return float4(toneMappedSrgbColor, 1.0);
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#version 460
|
|
||||||
#extension GL_EXT_nonuniform_qualifier : require
|
|
||||||
#extension GL_EXT_scalar_block_layout : require
|
|
||||||
#extension GL_EXT_shader_16bit_storage : require
|
|
||||||
|
|
||||||
layout(location = 0) in vec3 positionOS;
|
|
||||||
layout(location = 1) in vec2 texCoord;
|
|
||||||
layout(location = 2) in vec3 normalOS;
|
|
||||||
layout(location = 3) in vec4 tangentOS;
|
|
||||||
|
|
||||||
out Varyings {
|
|
||||||
layout(location = 0) flat uint instance;
|
|
||||||
layout(location = 1) vec3 positionVS;
|
|
||||||
layout(location = 2) vec2 texCoord;
|
|
||||||
layout(location = 3) vec3 normalVS;
|
|
||||||
layout(location = 4) vec3 tangentVS;
|
|
||||||
layout(location = 5) vec3 bitangentVS;
|
|
||||||
} var;
|
|
||||||
|
|
||||||
#include "includes/main_common.glsl"
|
|
||||||
|
|
||||||
#define OBJECT _Object[gl_InstanceIndex]
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec3 positionWS = (OBJECT.matrixOStoWS * vec4(positionOS, 1.0)).xyz;
|
|
||||||
vec3 positionVS = (_Global.matrixWStoVS * vec4(positionWS, 1.0)).xyz;
|
|
||||||
vec4 positionCS = _Global.matrixVStoCS * vec4(positionVS, 1.0);
|
|
||||||
|
|
||||||
vec3 normalWS = normalize((OBJECT.matrixOStoWSNormal * vec4(normalOS, 0.0)).xyz);
|
|
||||||
vec3 normalVS = normalize((_Global.matrixWStoVS * vec4(normalWS, 0.0)).xyz);
|
|
||||||
|
|
||||||
vec3 tangentWS = normalize((OBJECT.matrixOStoWSNormal * vec4(tangentOS.xyz, 0.0)).xyz);
|
|
||||||
vec3 tangentVS = normalize((_Global.matrixWStoVS * vec4(tangentWS, 0.0)).xyz);
|
|
||||||
|
|
||||||
vec3 bitangentVS = tangentOS.w * normalize(cross(normalVS, tangentVS));
|
|
||||||
|
|
||||||
gl_Position = positionCS;
|
|
||||||
var.instance = gl_InstanceIndex;
|
|
||||||
var.positionVS = positionVS;
|
|
||||||
var.texCoord = texCoord;
|
|
||||||
var.normalVS = normalVS;
|
|
||||||
var.tangentVS = tangentVS;
|
|
||||||
var.bitangentVS = bitangentVS;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#version 460
|
|
||||||
|
|
||||||
in Varyings {
|
|
||||||
layout(location = 0) vec3 texCoord;
|
|
||||||
} var;
|
|
||||||
|
|
||||||
layout(set = 0, binding = 1) uniform sampler _Sampler;
|
|
||||||
layout(set = 0, binding = 2) uniform textureCube _Texture;
|
|
||||||
|
|
||||||
layout(location = 0) out vec4 fragColor;
|
|
||||||
|
|
||||||
#include "includes/tone_mapping.glsl"
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texel = texture(samplerCube(_Texture, _Sampler), var.texCoord);
|
|
||||||
vec3 outgoingRadiance = texel.rgb;
|
|
||||||
|
|
||||||
vec3 toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance);
|
|
||||||
vec3 toneMappedSrgbColor = pow(toneMappedLinearColor, vec3(1.0 / 2.2));
|
|
||||||
|
|
||||||
fragColor = vec4(toneMappedSrgbColor, 1.0);
|
|
||||||
}
|
|
||||||
38
assets/shaders/skybox.slang
Normal file
38
assets/shaders/skybox.slang
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import "includes/math";
|
||||||
|
import "includes/structs";
|
||||||
|
|
||||||
|
[[vk::binding(0, 0)]] ConstantBuffer<GlobalUniforms> _Global;
|
||||||
|
[[vk::binding(1, 0)]] SamplerState _Sampler;
|
||||||
|
[[vk::binding(2, 0)]] TextureCube _Texture;
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
[[vk::location(0)]] float3 directionWS;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Varyings {
|
||||||
|
float4 positionCS : SV_Position;
|
||||||
|
float3 texCoord;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("vertex")]
|
||||||
|
Varyings vertexMain(Vertex vert) {
|
||||||
|
Varyings out;
|
||||||
|
|
||||||
|
let directionVS = mul(_Global.matrixWStoVS, float4(vert.directionWS, 0.0));
|
||||||
|
let directionCS = mul(_Global.matrixVStoCS, float4(directionVS, 0.0));
|
||||||
|
|
||||||
|
out.positionCS = float4(directionCS.xy, 0.0, directionCS.w);
|
||||||
|
out.texCoord = -vert.directionWS;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("fragment")]
|
||||||
|
float4 fragmentMain(Varyings frag) : SV_Target {
|
||||||
|
let texel = _Texture.Sample(_Sampler, frag.texCoord);
|
||||||
|
let outgoingRadiance = texel.rgb;
|
||||||
|
|
||||||
|
let toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance);
|
||||||
|
let toneMappedSrgbColor = linearToSrgbColor(toneMappedLinearColor);
|
||||||
|
|
||||||
|
return float4(toneMappedSrgbColor, 1.0);
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#version 460
|
|
||||||
#extension GL_EXT_scalar_block_layout : require
|
|
||||||
|
|
||||||
layout(location = 0) in vec3 directionWS;
|
|
||||||
|
|
||||||
out Varyings {
|
|
||||||
layout(location = 0) vec3 texCoord;
|
|
||||||
} var;
|
|
||||||
|
|
||||||
#include "includes/global_uniforms.glsl"
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec3 directionVS = (_Global.matrixWStoVS * vec4(directionWS, 0.0)).xyz;
|
|
||||||
vec4 directionCS = _Global.matrixVStoCS * vec4(directionVS, 0.0);
|
|
||||||
|
|
||||||
gl_Position = vec4(directionCS.xy, 0.0, directionCS.w);
|
|
||||||
var.texCoord = -directionWS;
|
|
||||||
}
|
|
||||||
36
build.zig
36
build.zig
@@ -48,6 +48,8 @@ pub fn build(b: *std.Build) !void {
|
|||||||
exe_mod.addImport("zglfw", zglfw_mod);
|
exe_mod.addImport("zglfw", zglfw_mod);
|
||||||
exe_mod.linkLibrary(zglfw_lib);
|
exe_mod.linkLibrary(zglfw_lib);
|
||||||
|
|
||||||
|
try addShaders(b, exe_mod);
|
||||||
|
|
||||||
const options = b.addOptions();
|
const options = b.addOptions();
|
||||||
options.addOption([]const u8, "version", zon.version);
|
options.addOption([]const u8, "version", zon.version);
|
||||||
exe_mod.addOptions("config", options);
|
exe_mod.addOptions("config", options);
|
||||||
@@ -70,3 +72,37 @@ pub fn build(b: *std.Build) !void {
|
|||||||
const run_step = b.step("run", "Run the game");
|
const run_step = b.step("run", "Run the game");
|
||||||
run_step.dependOn(&run_cmd.step);
|
run_step.dependOn(&run_cmd.step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn addShaders(b: *std.Build, exe_mod: *std.Build.Module) !void {
|
||||||
|
var dir = try b.build_root.handle.openDir(b.graph.io, "assets/shaders", .{ .iterate = true });
|
||||||
|
defer dir.close(b.graph.io);
|
||||||
|
|
||||||
|
var it = dir.iterateAssumeFirstIteration();
|
||||||
|
|
||||||
|
while (try it.next(b.graph.io)) |entry| {
|
||||||
|
if (entry.kind != .file) continue;
|
||||||
|
|
||||||
|
const stem = std.fs.path.stem(entry.name);
|
||||||
|
const extension = std.fs.path.extension(entry.name);
|
||||||
|
if (!std.mem.eql(u8, extension, ".slang")) continue;
|
||||||
|
|
||||||
|
const slangc = b.addSystemCommand(&.{
|
||||||
|
"slangc",
|
||||||
|
"-g",
|
||||||
|
"-target",
|
||||||
|
"spirv",
|
||||||
|
"-fvk-use-scalar-layout",
|
||||||
|
"-matrix-layout-column-major",
|
||||||
|
});
|
||||||
|
|
||||||
|
slangc.addArg("-o");
|
||||||
|
const spv = slangc.addOutputFileArg(b.fmt("{s}.spv", .{stem}));
|
||||||
|
|
||||||
|
slangc.addArg("-depfile");
|
||||||
|
_ = slangc.addDepFileOutputArg(b.fmt("{s}.d", .{stem}));
|
||||||
|
|
||||||
|
slangc.addFileArg(b.path(b.pathJoin(&.{ "assets/shaders", entry.name })));
|
||||||
|
|
||||||
|
exe_mod.addAnonymousImport(b.fmt("shaders/{s}", .{stem}), .{ .root_source_file = spv });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
2
castle
2
castle
Submodule castle updated: dc839e098c...99b8cf1d8e
@@ -1,9 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
glslc -g --target-env=vulkan1.2 assets/shaders/equirect_to_cube.comp -o src/shaders/equirect_to_cube_comp.spv
|
|
||||||
glslc -g --target-env=vulkan1.2 assets/shaders/gui_box.frag -o src/shaders/gui_box_frag.spv
|
|
||||||
glslc -g --target-env=vulkan1.2 assets/shaders/gui_box.vert -o src/shaders/gui_box_vert.spv
|
|
||||||
glslc -g --target-env=vulkan1.2 assets/shaders/main.frag -o src/shaders/main_frag.spv
|
|
||||||
glslc -g --target-env=vulkan1.2 assets/shaders/main.vert -o src/shaders/main_vert.spv
|
|
||||||
glslc -g --target-env=vulkan1.2 assets/shaders/skybox.frag -o src/shaders/skybox_frag.spv
|
|
||||||
glslc -g --target-env=vulkan1.2 assets/shaders/skybox.vert -o src/shaders/skybox_vert.spv
|
|
||||||
@@ -10,6 +10,7 @@ const media = @import("media");
|
|||||||
const glfw = @import("zglfw");
|
const glfw = @import("zglfw");
|
||||||
|
|
||||||
const Atoms = @import("engine/Atoms.zig");
|
const Atoms = @import("engine/Atoms.zig");
|
||||||
|
const Fonts = @import("engine/Fonts.zig");
|
||||||
const Engine = @import("engine/Engine.zig");
|
const Engine = @import("engine/Engine.zig");
|
||||||
const Gui = @import("engine/Gui.zig");
|
const Gui = @import("engine/Gui.zig");
|
||||||
const Materials = @import("engine/Materials.zig");
|
const Materials = @import("engine/Materials.zig");
|
||||||
@@ -23,6 +24,7 @@ pub var allocator_persistent: std.mem.Allocator = undefined;
|
|||||||
pub var io: std.Io = undefined;
|
pub var io: std.Io = undefined;
|
||||||
pub var vk_allocator: *VkAllocator = undefined;
|
pub var vk_allocator: *VkAllocator = undefined;
|
||||||
pub var stbi: *media.stbi = undefined;
|
pub var stbi: *media.stbi = undefined;
|
||||||
|
pub var stbtt: *media.stbtt = undefined;
|
||||||
|
|
||||||
pub var window: *glfw.Window = undefined;
|
pub var window: *glfw.Window = undefined;
|
||||||
pub var atoms: *Atoms = undefined;
|
pub var atoms: *Atoms = undefined;
|
||||||
@@ -31,4 +33,5 @@ pub var swapchain: *Swapchain = undefined;
|
|||||||
|
|
||||||
pub var textures: *Textures = undefined;
|
pub var textures: *Textures = undefined;
|
||||||
pub var materials: *Materials = undefined;
|
pub var materials: *Materials = undefined;
|
||||||
|
pub var fonts: *Fonts = undefined;
|
||||||
pub var gui: *Gui = undefined;
|
pub var gui: *Gui = undefined;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const Chunk = @This();
|
const Chunk = @This();
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const c = @import("const.zig");
|
||||||
const ctx = @import("AppContext.zig");
|
const ctx = @import("AppContext.zig");
|
||||||
const math = @import("math.zig");
|
const math = @import("math.zig");
|
||||||
const shaders = @import("shaders.zig");
|
const shaders = @import("shaders.zig");
|
||||||
@@ -15,9 +16,7 @@ const Game = @import("Game.zig");
|
|||||||
const GenericBuffer = @import("engine/GenericBuffer.zig").GenericBuffer;
|
const GenericBuffer = @import("engine/GenericBuffer.zig").GenericBuffer;
|
||||||
const Materials = @import("engine/Materials.zig");
|
const Materials = @import("engine/Materials.zig");
|
||||||
|
|
||||||
const ObjectUniformsBuffer = GenericBuffer(void, shaders.ObjectUniforms);
|
const chunk_size = c.vx_per_ck;
|
||||||
|
|
||||||
pub const chunk_size = 16;
|
|
||||||
|
|
||||||
const initial_capacity = 256;
|
const initial_capacity = 256;
|
||||||
var next_chunk_id: std.atomic.Value(u64) = .init(0);
|
var next_chunk_id: std.atomic.Value(u64) = .init(0);
|
||||||
@@ -27,7 +26,7 @@ var next_chunk_id: std.atomic.Value(u64) = .init(0);
|
|||||||
blocks: [chunk_size][chunk_size][chunk_size]Blocks.Id,
|
blocks: [chunk_size][chunk_size][chunk_size]Blocks.Id,
|
||||||
origin: vm.Vector3,
|
origin: vm.Vector3,
|
||||||
descriptor_set: vk.DescriptorSet,
|
descriptor_set: vk.DescriptorSet,
|
||||||
object_buffer: ObjectUniformsBuffer,
|
object_buffer: shaders.ObjectUniformsBuffer,
|
||||||
object_count: u32,
|
object_count: u32,
|
||||||
|
|
||||||
/// For debug purposes, a unique "name" per chunk instance.
|
/// For debug purposes, a unique "name" per chunk instance.
|
||||||
@@ -50,10 +49,10 @@ pub fn init(init_info: InitInfo) !Chunk {
|
|||||||
});
|
});
|
||||||
errdefer engine.freeDescriptorSet(init_info.descriptor_pool, descriptor_set);
|
errdefer engine.freeDescriptorSet(init_info.descriptor_pool, descriptor_set);
|
||||||
|
|
||||||
var object_buffer: ObjectUniformsBuffer = try .init(.{
|
var object_buffer: shaders.ObjectUniformsBuffer = try .init(.{
|
||||||
.usage = .storage,
|
.usage = .storage,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
.array_capacity = initial_capacity,
|
.capacity = initial_capacity,
|
||||||
});
|
});
|
||||||
errdefer object_buffer.deinit();
|
errdefer object_buffer.deinit();
|
||||||
|
|
||||||
@@ -120,13 +119,18 @@ pub fn refresh(self: *Chunk, blocks: *const Blocks, neighbors: Neighbors) !void
|
|||||||
|
|
||||||
inline for (@typeInfo(voxels.Orientation).@"enum".fields) |field| {
|
inline for (@typeInfo(voxels.Orientation).@"enum".fields) |field| {
|
||||||
const side = @field(voxels.Orientation, field.name);
|
const side = @field(voxels.Orientation, field.name);
|
||||||
const material = @field(block.walls, field.name).material;
|
const wall: Blocks.Definition.Wall = @field(block.walls, field.name);
|
||||||
if (material != .empty and self.getOpposite(side, ix, iy, iz, blocks, neighbors) == .empty) {
|
if (wall.material != .empty and self.getOpposite(side, ix, iy, iz, blocks, neighbors) == .empty) {
|
||||||
const matrix = getMatrix(side, origin);
|
const matrix = getMatrix(side, origin);
|
||||||
try uniforms.append(allocator_frame, .{
|
try uniforms.append(allocator_frame, .{
|
||||||
.matrixOStoWS = matrix,
|
.matrixOStoWS = .{
|
||||||
.matrixOStoWSNormal = matrix,
|
matrix.getIVersor().dropW(),
|
||||||
.material = material,
|
matrix.getJVersor().dropW(),
|
||||||
|
matrix.getKVersor().dropW(),
|
||||||
|
matrix.getTranslationVector().dropW(),
|
||||||
|
},
|
||||||
|
.texCoordTransform = wall.transform.getMatrix(),
|
||||||
|
.material = wall.material,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,12 +139,12 @@ pub fn refresh(self: *Chunk, blocks: *const Blocks, neighbors: Neighbors) !void
|
|||||||
}
|
}
|
||||||
|
|
||||||
const object_count: u32 = @intCast(uniforms.items.len);
|
const object_count: u32 = @intCast(uniforms.items.len);
|
||||||
if (self.object_buffer.array_capacity < object_count) {
|
if (self.object_buffer.capacity < object_count) {
|
||||||
const desired_capacity = std.math.ceilPowerOfTwoAssert(u32, object_count);
|
const desired_capacity = std.math.ceilPowerOfTwoAssert(u32, object_count);
|
||||||
const new_object_buffer: ObjectUniformsBuffer = try .init(.{
|
const new_object_buffer: shaders.ObjectUniformsBuffer = try .init(.{
|
||||||
.usage = .storage,
|
.usage = .storage,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
.array_capacity = desired_capacity,
|
.capacity = desired_capacity,
|
||||||
});
|
});
|
||||||
|
|
||||||
try engine.updateDescriptorSets(.{
|
try engine.updateDescriptorSets(.{
|
||||||
@@ -169,7 +173,7 @@ pub fn refresh(self: *Chunk, blocks: *const Blocks, neighbors: Neighbors) !void
|
|||||||
engine.setObjectName(new_object_buffer.device_memory, "DM Chunk[{d}]", .{self.chunk_id});
|
engine.setObjectName(new_object_buffer.device_memory, "DM Chunk[{d}]", .{self.chunk_id});
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.object_buffer.write(.{ .elements = uniforms.items });
|
try self.object_buffer.writeSlice(uniforms.items);
|
||||||
self.object_count = object_count;
|
self.object_count = object_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
280
src/Game.zig
280
src/Game.zig
@@ -16,11 +16,11 @@ const Chunk = @import("Chunk.zig");
|
|||||||
const Chunks = @import("Chunks.zig");
|
const Chunks = @import("Chunks.zig");
|
||||||
const CommandBuffer = @import("engine/CommandBuffer.zig");
|
const CommandBuffer = @import("engine/CommandBuffer.zig");
|
||||||
const Engine = @import("engine/Engine.zig");
|
const Engine = @import("engine/Engine.zig");
|
||||||
|
const Fonts = @import("engine/Fonts.zig");
|
||||||
const Iterator2 = math.Iterator2;
|
const Iterator2 = math.Iterator2;
|
||||||
const Materials = @import("engine/Materials.zig");
|
const Materials = @import("engine/Materials.zig");
|
||||||
const Player = @import("Player.zig");
|
const Player = @import("Player.zig");
|
||||||
const Skybox = @import("engine/Skybox.zig");
|
const Skybox = @import("engine/Skybox.zig");
|
||||||
const StagingBuffer = @import("engine/StagingBuffer.zig");
|
|
||||||
const Swapchain = @import("engine/Swapchain.zig");
|
const Swapchain = @import("engine/Swapchain.zig");
|
||||||
const Texture = @import("engine/Texture.zig");
|
const Texture = @import("engine/Texture.zig");
|
||||||
const Textures = @import("engine/Textures.zig");
|
const Textures = @import("engine/Textures.zig");
|
||||||
@@ -36,7 +36,8 @@ vertex_buffer: shaders.VertexBuffer,
|
|||||||
index_buffer: shaders.IndexBuffer,
|
index_buffer: shaders.IndexBuffer,
|
||||||
|
|
||||||
global_uniforms: shaders.GlobalUniformsBuffer,
|
global_uniforms: shaders.GlobalUniformsBuffer,
|
||||||
global_uniforms_staging_buffer: StagingBuffer,
|
global_uniforms_staging_buffer: shaders.GlobalUniformsBuffer,
|
||||||
|
global_uniforms_staging_ptr: *shaders.GlobalUniforms,
|
||||||
global_uniforms_transfer_semaphores: []vk.Semaphore,
|
global_uniforms_transfer_semaphores: []vk.Semaphore,
|
||||||
point_lights: shaders.PointLightBuffer,
|
point_lights: shaders.PointLightBuffer,
|
||||||
directional_lights: shaders.DirectionalLightBuffer,
|
directional_lights: shaders.DirectionalLightBuffer,
|
||||||
@@ -47,14 +48,22 @@ blocks: Blocks,
|
|||||||
chunks: Chunks,
|
chunks: Chunks,
|
||||||
skybox: Skybox,
|
skybox: Skybox,
|
||||||
player: Player,
|
player: Player,
|
||||||
|
font: Fonts.Id,
|
||||||
|
|
||||||
const max_textures = 1024;
|
|
||||||
const max_point_lights = 1024;
|
const max_point_lights = 1024;
|
||||||
const max_directional_lights = 4;
|
const max_directional_lights = 4;
|
||||||
const chunk_descriptor_pool = 1024;
|
const chunk_descriptor_pool = 1024;
|
||||||
|
|
||||||
const camera_near_plane = 0.1;
|
const camera_near_plane = 0.1;
|
||||||
|
|
||||||
|
const point_lights_data = [_]shaders.PointLight{};
|
||||||
|
const directional_lights_data = [_]shaders.DirectionalLight{
|
||||||
|
.{
|
||||||
|
.directionWS = .unit_nz,
|
||||||
|
.color = .init(0.3, 0.3, 0.3),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
pub fn init() !Game {
|
pub fn init() !Game {
|
||||||
const allocator_general = ctx.allocator_general;
|
const allocator_general = ctx.allocator_general;
|
||||||
const allocator_frame = ctx.allocator_frame;
|
const allocator_frame = ctx.allocator_frame;
|
||||||
@@ -62,6 +71,7 @@ pub fn init() !Game {
|
|||||||
const swapchain = ctx.swapchain;
|
const swapchain = ctx.swapchain;
|
||||||
const textures = ctx.textures;
|
const textures = ctx.textures;
|
||||||
const materials = ctx.materials;
|
const materials = ctx.materials;
|
||||||
|
const fonts = ctx.fonts;
|
||||||
const gui = ctx.gui;
|
const gui = ctx.gui;
|
||||||
|
|
||||||
var blocks = try Blocks.init();
|
var blocks = try Blocks.init();
|
||||||
@@ -76,9 +86,9 @@ pub fn init() !Game {
|
|||||||
.mag_filter = .linear,
|
.mag_filter = .linear,
|
||||||
.min_filter = .linear,
|
.min_filter = .linear,
|
||||||
.mipmap_mode = .linear,
|
.mipmap_mode = .linear,
|
||||||
.address_mode_u = .repeat,
|
.address_mode_u = .clamp_to_edge,
|
||||||
.address_mode_v = .repeat,
|
.address_mode_v = .clamp_to_edge,
|
||||||
.address_mode_w = .repeat,
|
.address_mode_w = .clamp_to_edge,
|
||||||
.mip_lod_bias = 0,
|
.mip_lod_bias = 0,
|
||||||
.anisotropy_enable = .false,
|
.anisotropy_enable = .false,
|
||||||
.max_anisotropy = 0,
|
.max_anisotropy = 0,
|
||||||
@@ -127,9 +137,8 @@ pub fn init() !Game {
|
|||||||
.{
|
.{
|
||||||
.binding = 5,
|
.binding = 5,
|
||||||
.descriptor_type = .sampled_image,
|
.descriptor_type = .sampled_image,
|
||||||
.descriptor_count = max_textures,
|
.descriptor_count = Textures.max_textures,
|
||||||
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
.flags = .{ .variable_descriptor_count_bit = true },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -158,60 +167,52 @@ pub fn init() !Game {
|
|||||||
errdefer engine.destroyPipelineLayout(pipeline_layout);
|
errdefer engine.destroyPipelineLayout(pipeline_layout);
|
||||||
engine.setObjectName(pipeline_layout, "PL Main", .{});
|
engine.setObjectName(pipeline_layout, "PL Main", .{});
|
||||||
|
|
||||||
const vertex_shader = try engine.createShaderModule(.{ .code = &shaders.main_vert_spv });
|
const shader = try engine.createShaderModule(.{ .code = &shaders.main_spv });
|
||||||
defer engine.destroyShaderModule(vertex_shader);
|
defer engine.destroyShaderModule(shader);
|
||||||
engine.setObjectName(vertex_shader, "SM main_vert", .{});
|
engine.setObjectName(shader, "SM main", .{});
|
||||||
|
|
||||||
const fragment_shader = try engine.createShaderModule(.{ .code = &shaders.main_frag_spv });
|
|
||||||
defer engine.destroyShaderModule(fragment_shader);
|
|
||||||
engine.setObjectName(fragment_shader, "SM main_frag", .{});
|
|
||||||
|
|
||||||
var vertex_buffer = try shaders.VertexBuffer.init(.{
|
var vertex_buffer = try shaders.VertexBuffer.init(.{
|
||||||
.usage = .vertex,
|
.usage = .vertex,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
.array_capacity = 4,
|
.capacity = 4,
|
||||||
.name = "QuadVB",
|
.name = "QuadVB",
|
||||||
});
|
});
|
||||||
errdefer vertex_buffer.deinit();
|
errdefer vertex_buffer.deinit();
|
||||||
try vertex_buffer.write(.{
|
try vertex_buffer.writeSlice(&.{
|
||||||
.elements = &.{
|
.init(
|
||||||
.init(
|
.init(0, 0, 0),
|
||||||
.init(0, 0, 0),
|
.init(0, 1),
|
||||||
.init(0, 1),
|
.init(0, 0, 1),
|
||||||
.init(0, 0, 1),
|
.init(1, 0, 0, -1),
|
||||||
.init(1, 0, 0, -1),
|
),
|
||||||
),
|
.init(
|
||||||
.init(
|
.init(1, 0, 0),
|
||||||
.init(1, 0, 0),
|
.init(1, 1),
|
||||||
.init(1, 1),
|
.init(0, 0, 1),
|
||||||
.init(0, 0, 1),
|
.init(1, 0, 0, -1),
|
||||||
.init(1, 0, 0, -1),
|
),
|
||||||
),
|
.init(
|
||||||
.init(
|
.init(0, 1, 0),
|
||||||
.init(0, 1, 0),
|
.init(0, 0),
|
||||||
.init(0, 0),
|
.init(0, 0, 1),
|
||||||
.init(0, 0, 1),
|
.init(1, 0, 0, -1),
|
||||||
.init(1, 0, 0, -1),
|
),
|
||||||
),
|
.init(
|
||||||
.init(
|
.init(1, 1, 0),
|
||||||
.init(1, 1, 0),
|
.init(1, 0),
|
||||||
.init(1, 0),
|
.init(0, 0, 1),
|
||||||
.init(0, 0, 1),
|
.init(1, 0, 0, -1),
|
||||||
.init(1, 0, 0, -1),
|
),
|
||||||
),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var index_buffer = try shaders.IndexBuffer.init(.{
|
var index_buffer = try shaders.IndexBuffer.init(.{
|
||||||
.usage = .index,
|
.usage = .index,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
.array_capacity = 6,
|
.capacity = 6,
|
||||||
.name = "QuadIB",
|
.name = "QuadIB",
|
||||||
});
|
});
|
||||||
errdefer index_buffer.deinit();
|
errdefer index_buffer.deinit();
|
||||||
try index_buffer.write(.{
|
try index_buffer.writeSlice(&.{ 0, 1, 2, 2, 1, 3 });
|
||||||
.elements = &.{ 0, 1, 2, 2, 1, 3 },
|
|
||||||
});
|
|
||||||
|
|
||||||
var global_uniforms = try shaders.GlobalUniformsBuffer.init(.{
|
var global_uniforms = try shaders.GlobalUniformsBuffer.init(.{
|
||||||
.usage = .uniform,
|
.usage = .uniform,
|
||||||
@@ -220,12 +221,17 @@ pub fn init() !Game {
|
|||||||
});
|
});
|
||||||
errdefer global_uniforms.deinit();
|
errdefer global_uniforms.deinit();
|
||||||
|
|
||||||
var global_uniforms_staging_buffer = try StagingBuffer.init(.{
|
var global_uniforms_staging_buffer = try shaders.GlobalUniformsBuffer.init(.{
|
||||||
.capacity = @sizeOf(shaders.GlobalUniforms),
|
.usage = .staging,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
|
.mappable = true,
|
||||||
|
.name = "GlobalUniforms Staging",
|
||||||
});
|
});
|
||||||
errdefer global_uniforms_staging_buffer.deinit();
|
errdefer global_uniforms_staging_buffer.deinit();
|
||||||
|
|
||||||
|
const global_uniforms_staging_ptr = try global_uniforms_staging_buffer.map();
|
||||||
|
errdefer global_uniforms_staging_buffer.unmap();
|
||||||
|
|
||||||
const global_uniforms_transfer_semaphores = blk: {
|
const global_uniforms_transfer_semaphores = blk: {
|
||||||
var semaphores: std.ArrayList(vk.Semaphore) = try .initCapacity(allocator_general, swapchain.swapchain_images.len);
|
var semaphores: std.ArrayList(vk.Semaphore) = try .initCapacity(allocator_general, swapchain.swapchain_images.len);
|
||||||
errdefer semaphores.deinit(allocator_general);
|
errdefer semaphores.deinit(allocator_general);
|
||||||
@@ -250,7 +256,7 @@ pub fn init() !Game {
|
|||||||
var point_lights = try shaders.PointLightBuffer.init(.{
|
var point_lights = try shaders.PointLightBuffer.init(.{
|
||||||
.usage = .storage,
|
.usage = .storage,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
.array_capacity = max_point_lights,
|
.capacity = max_point_lights,
|
||||||
.name = "PointLights",
|
.name = "PointLights",
|
||||||
});
|
});
|
||||||
errdefer point_lights.deinit();
|
errdefer point_lights.deinit();
|
||||||
@@ -258,7 +264,7 @@ pub fn init() !Game {
|
|||||||
var directional_lights = try shaders.DirectionalLightBuffer.init(.{
|
var directional_lights = try shaders.DirectionalLightBuffer.init(.{
|
||||||
.usage = .storage,
|
.usage = .storage,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
.array_capacity = max_directional_lights,
|
.capacity = max_directional_lights,
|
||||||
.name = "DirectionalLights",
|
.name = "DirectionalLights",
|
||||||
});
|
});
|
||||||
errdefer directional_lights.deinit();
|
errdefer directional_lights.deinit();
|
||||||
@@ -267,13 +273,13 @@ pub fn init() !Game {
|
|||||||
.stages = &.{
|
.stages = &.{
|
||||||
.{
|
.{
|
||||||
.stage = .{ .vertex_bit = true },
|
.stage = .{ .vertex_bit = true },
|
||||||
.module = vertex_shader,
|
.module = shader,
|
||||||
.name = "main",
|
.name = "vertexMain",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.stage = .{ .fragment_bit = true },
|
.stage = .{ .fragment_bit = true },
|
||||||
.module = fragment_shader,
|
.module = shader,
|
||||||
.name = "main",
|
.name = "fragmentMain",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.vertex_input_state = .{
|
.vertex_input_state = .{
|
||||||
@@ -411,7 +417,7 @@ pub fn init() !Game {
|
|||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.type = .sampled_image,
|
.type = .sampled_image,
|
||||||
.descriptor_count = max_textures,
|
.descriptor_count = Textures.max_textures,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.type = .uniform_buffer,
|
.type = .uniform_buffer,
|
||||||
@@ -429,7 +435,6 @@ pub fn init() !Game {
|
|||||||
const global_descriptor_set = try engine.allocateDescriptorSet(.{
|
const global_descriptor_set = try engine.allocateDescriptorSet(.{
|
||||||
.descriptor_pool = descriptor_pool,
|
.descriptor_pool = descriptor_pool,
|
||||||
.set_layout = global_descriptor_set_layout,
|
.set_layout = global_descriptor_set_layout,
|
||||||
.variable_descriptor_count = @intCast(textures.array.items.len),
|
|
||||||
});
|
});
|
||||||
engine.setObjectName(global_descriptor_set, "DS Global", .{});
|
engine.setObjectName(global_descriptor_set, "DS Global", .{});
|
||||||
|
|
||||||
@@ -438,6 +443,13 @@ pub fn init() !Game {
|
|||||||
const block_stone = try blocks.getOrLoad("Stone.json");
|
const block_stone = try blocks.getOrLoad("Stone.json");
|
||||||
const block_bedrock = try blocks.getOrLoad("Bedrock.json");
|
const block_bedrock = try blocks.getOrLoad("Bedrock.json");
|
||||||
|
|
||||||
|
const font_id = try fonts.load("FiraMono-Regular.ttf", .{
|
||||||
|
.codepoint_ranges = &.{
|
||||||
|
.{ .start = 32, .end = 127 },
|
||||||
|
},
|
||||||
|
.name = "Fira Mono",
|
||||||
|
});
|
||||||
|
|
||||||
// VOLATILE Load all assets before this point
|
// VOLATILE Load all assets before this point
|
||||||
|
|
||||||
const descriptor_images = try allocator_frame.alloc(vk.DescriptorImageInfo, textures.array.items.len);
|
const descriptor_images = try allocator_frame.alloc(vk.DescriptorImageInfo, textures.array.items.len);
|
||||||
@@ -609,26 +621,14 @@ pub fn init() !Game {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const point_lights_data: []const shaders.PointLight = &.{};
|
try point_lights.writeSlice(&point_lights_data);
|
||||||
try point_lights.write(.{
|
try directional_lights.writeSlice(&directional_lights_data);
|
||||||
.header = @intCast(point_lights_data.len),
|
|
||||||
.elements = point_lights_data,
|
|
||||||
});
|
|
||||||
|
|
||||||
const directional_lights_data: []const shaders.DirectionalLight = &.{
|
|
||||||
.{
|
|
||||||
.directionWS = .unit_nz,
|
|
||||||
.color = .init(0.3, 0.3, 0.3),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
try directional_lights.write(.{
|
|
||||||
.header = @intCast(directional_lights_data.len),
|
|
||||||
.elements = directional_lights_data,
|
|
||||||
});
|
|
||||||
|
|
||||||
var skybox = try Skybox.load("skybox.hdr", 512, global_uniforms.buffer);
|
var skybox = try Skybox.load("skybox.hdr", 512, global_uniforms.buffer);
|
||||||
errdefer skybox.deinit();
|
errdefer skybox.deinit();
|
||||||
|
|
||||||
|
const font = &fonts.array.items[@intFromEnum(font_id)];
|
||||||
|
|
||||||
// TODO This coupling is not elegant - the game must provide the GUI with
|
// TODO This coupling is not elegant - the game must provide the GUI with
|
||||||
// uniform buffer for globals. Maybe GUI should just use their own global
|
// uniform buffer for globals. Maybe GUI should just use their own global
|
||||||
// uniform buffer not for any other reason than decoupling. After all, GUI
|
// uniform buffer not for any other reason than decoupling. After all, GUI
|
||||||
@@ -651,6 +651,88 @@ pub fn init() !Game {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
.{
|
||||||
|
.dst_set = gui.text_descriptor_set,
|
||||||
|
.dst_binding = 0,
|
||||||
|
.dst_array_element = 0,
|
||||||
|
.descriptor_type = .uniform_buffer,
|
||||||
|
.descriptor_infos = .{
|
||||||
|
.buffer = &.{
|
||||||
|
.{
|
||||||
|
.buffer = global_uniforms.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.range = vk.WHOLE_SIZE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.dst_set = gui.text_descriptor_set,
|
||||||
|
.dst_binding = 2,
|
||||||
|
.dst_array_element = @intFromEnum(font_id),
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_infos = .{
|
||||||
|
.buffer = &.{
|
||||||
|
.{
|
||||||
|
.buffer = font.glyph_data.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.range = vk.WHOLE_SIZE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.dst_set = gui.text_descriptor_set,
|
||||||
|
.dst_binding = 3,
|
||||||
|
.dst_array_element = @intFromEnum(font_id),
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_infos = .{
|
||||||
|
.buffer = &.{
|
||||||
|
.{
|
||||||
|
.buffer = font.control_points.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.range = vk.WHOLE_SIZE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.dst_set = gui.text_descriptor_set,
|
||||||
|
.dst_binding = 4,
|
||||||
|
.dst_array_element = @intFromEnum(font_id),
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_infos = .{
|
||||||
|
.buffer = &.{
|
||||||
|
.{
|
||||||
|
.buffer = font.band_data.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.range = vk.WHOLE_SIZE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.dst_set = gui.image_descriptor_set,
|
||||||
|
.dst_binding = 0,
|
||||||
|
.dst_array_element = 0,
|
||||||
|
.descriptor_type = .uniform_buffer,
|
||||||
|
.descriptor_infos = .{
|
||||||
|
.buffer = &.{
|
||||||
|
.{
|
||||||
|
.buffer = global_uniforms.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.range = vk.WHOLE_SIZE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.dst_set = gui.image_descriptor_set,
|
||||||
|
.dst_binding = 3,
|
||||||
|
.dst_array_element = 0,
|
||||||
|
.descriptor_type = .sampled_image,
|
||||||
|
.descriptor_infos = .{ .image = descriptor_images },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -667,6 +749,7 @@ pub fn init() !Game {
|
|||||||
|
|
||||||
.global_uniforms = global_uniforms,
|
.global_uniforms = global_uniforms,
|
||||||
.global_uniforms_staging_buffer = global_uniforms_staging_buffer,
|
.global_uniforms_staging_buffer = global_uniforms_staging_buffer,
|
||||||
|
.global_uniforms_staging_ptr = global_uniforms_staging_ptr,
|
||||||
.global_uniforms_transfer_semaphores = global_uniforms_transfer_semaphores,
|
.global_uniforms_transfer_semaphores = global_uniforms_transfer_semaphores,
|
||||||
.point_lights = point_lights,
|
.point_lights = point_lights,
|
||||||
.directional_lights = directional_lights,
|
.directional_lights = directional_lights,
|
||||||
@@ -677,6 +760,7 @@ pub fn init() !Game {
|
|||||||
.chunks = .{ .chunks = chunks },
|
.chunks = .{ .chunks = chunks },
|
||||||
.skybox = skybox,
|
.skybox = skybox,
|
||||||
.player = .init(player_position_sv, 0, 0),
|
.player = .init(player_position_sv, 0, 0),
|
||||||
|
.font = font_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -698,6 +782,7 @@ pub fn deinit(self: *Game) void {
|
|||||||
self.skybox.deinit();
|
self.skybox.deinit();
|
||||||
|
|
||||||
self.global_uniforms.deinit();
|
self.global_uniforms.deinit();
|
||||||
|
self.global_uniforms_staging_buffer.unmap();
|
||||||
self.global_uniforms_staging_buffer.deinit();
|
self.global_uniforms_staging_buffer.deinit();
|
||||||
self.point_lights.deinit();
|
self.point_lights.deinit();
|
||||||
self.directional_lights.deinit();
|
self.directional_lights.deinit();
|
||||||
@@ -719,37 +804,7 @@ pub fn deinit(self: *Game) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(self: *Game, dt: f32) void {
|
pub fn update(self: *Game, dt: f32) void {
|
||||||
const swapchain = ctx.swapchain;
|
self.player.update(dt, self);
|
||||||
const gui = ctx.gui;
|
|
||||||
|
|
||||||
self.player.update(dt, &self.chunks);
|
|
||||||
|
|
||||||
const extent = swapchain.extent;
|
|
||||||
const framebuffer_size = vm.Vector2.init(
|
|
||||||
@floatFromInt(extent.width),
|
|
||||||
@floatFromInt(extent.height),
|
|
||||||
);
|
|
||||||
|
|
||||||
const crosshair_half_extent_px = 8;
|
|
||||||
const crosshair_half_width_px = 1;
|
|
||||||
|
|
||||||
gui.pushBox(.{
|
|
||||||
.background_color = .init(1, 1, 1, 0.5),
|
|
||||||
.border_color = .init(0, 0, 0, 1),
|
|
||||||
.position_sspx = framebuffer_size.mulScalar(0.5).add(.init(-crosshair_half_extent_px, -crosshair_half_width_px)),
|
|
||||||
.size_px = .init(2.0 * crosshair_half_extent_px, 2.0 * crosshair_half_width_px),
|
|
||||||
.border_width_px = 0,
|
|
||||||
.border_radius_px = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
gui.pushBox(.{
|
|
||||||
.background_color = .init(1, 1, 1, 0.5),
|
|
||||||
.border_color = .init(0, 0, 0, 1),
|
|
||||||
.position_sspx = framebuffer_size.mulScalar(0.5).add(.init(-crosshair_half_width_px, -crosshair_half_extent_px)),
|
|
||||||
.size_px = .init(2.0 * crosshair_half_width_px, 2.0 * crosshair_half_extent_px),
|
|
||||||
.border_width_px = 0,
|
|
||||||
.border_radius_px = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.render() catch |err| {
|
self.render() catch |err| {
|
||||||
std.log.err("Failed to render: {s}", .{@errorName(err)});
|
std.log.err("Failed to render: {s}", .{@errorName(err)});
|
||||||
@@ -853,16 +908,21 @@ fn render(self: *Game) !void {
|
|||||||
|
|
||||||
const ambient_light = vm.Vector3.init(0.01, 0.01, 0.01);
|
const ambient_light = vm.Vector3.init(0.01, 0.01, 0.01);
|
||||||
|
|
||||||
const global_uniforms_data: shaders.GlobalUniforms = .{
|
self.global_uniforms_staging_ptr.* = .{
|
||||||
.matrixWStoVS = matrix_ws_to_vs,
|
.matrixWStoVS = .{
|
||||||
|
matrix_ws_to_vs.getIVersor().dropW(),
|
||||||
|
matrix_ws_to_vs.getJVersor().dropW(),
|
||||||
|
matrix_ws_to_vs.getKVersor().dropW(),
|
||||||
|
matrix_ws_to_vs.getTranslationVector().dropW(),
|
||||||
|
},
|
||||||
.matrixVStoCS = matrix_vs_to_cs,
|
.matrixVStoCS = matrix_vs_to_cs,
|
||||||
.matrixSSPXtoCS = matrix_sspx_to_cs,
|
.matrixSSPXtoCS = matrix_sspx_to_cs,
|
||||||
|
|
||||||
|
.pointLightCount = point_lights_data.len,
|
||||||
|
.directionalLightCount = directional_lights_data.len,
|
||||||
.ambientLight = ambient_light,
|
.ambientLight = ambient_light,
|
||||||
};
|
};
|
||||||
|
try self.global_uniforms_staging_buffer.flush(0, 1);
|
||||||
const staging_memory = try self.global_uniforms_staging_buffer.map();
|
|
||||||
@memcpy(staging_memory, std.mem.asBytes(&global_uniforms_data));
|
|
||||||
self.global_uniforms_staging_buffer.unmap();
|
|
||||||
|
|
||||||
try self.deferred_command_buffers.ensureUnusedCapacity(allocator_general, 1);
|
try self.deferred_command_buffers.ensureUnusedCapacity(allocator_general, 1);
|
||||||
const global_uniforms_transfer_command_buffer = try CommandBuffer.init(.transfer);
|
const global_uniforms_transfer_command_buffer = try CommandBuffer.init(.transfer);
|
||||||
|
|||||||
165
src/Player.zig
165
src/Player.zig
@@ -2,6 +2,7 @@ const Player = @This();
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const c = @import("const.zig");
|
const c = @import("const.zig");
|
||||||
|
const ctx = @import("AppContext.zig");
|
||||||
const glfw = @import("zglfw");
|
const glfw = @import("zglfw");
|
||||||
const math = @import("math.zig");
|
const math = @import("math.zig");
|
||||||
const vm = @import("vecmath");
|
const vm = @import("vecmath");
|
||||||
@@ -183,9 +184,9 @@ pub const blocks = [9][:0]const u8{
|
|||||||
"Bricks.json",
|
"Bricks.json",
|
||||||
"ChiseledStoneBricks.json",
|
"ChiseledStoneBricks.json",
|
||||||
"Cobblestone.json",
|
"Cobblestone.json",
|
||||||
"DiamondBlock.json",
|
"OakLogX.json",
|
||||||
"GoldBlock.json",
|
"OakLogY.json",
|
||||||
"IronBlock.json",
|
"OakLogZ.json",
|
||||||
"OakPlanks.json",
|
"OakPlanks.json",
|
||||||
"SmoothStone.json",
|
"SmoothStone.json",
|
||||||
"StoneBricks.json",
|
"StoneBricks.json",
|
||||||
@@ -231,7 +232,6 @@ pub fn onKeyDown(self: *Player, key: glfw.Key) void {
|
|||||||
const key_code = @intFromEnum(key);
|
const key_code = @intFromEnum(key);
|
||||||
if (key_code >= @intFromEnum(glfw.Key.one) and key_code <= @intFromEnum(glfw.Key.nine)) {
|
if (key_code >= @intFromEnum(glfw.Key.one) and key_code <= @intFromEnum(glfw.Key.nine)) {
|
||||||
self.block_index = @intCast(key_code - @intFromEnum(glfw.Key.one));
|
self.block_index = @intCast(key_code - @intFromEnum(glfw.Key.one));
|
||||||
std.log.info("Picked block {s}", .{blocks[self.block_index]});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +281,11 @@ pub fn onMouseDown(self: *Player, button: glfw.MouseButton, game: *Game) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(self: *Player, dt: f32, chunks: *const Chunks) void {
|
pub fn update(self: *Player, dt: f32, game: *Game) void {
|
||||||
|
const swapchain = ctx.swapchain;
|
||||||
|
const materials = ctx.materials;
|
||||||
|
const gui = ctx.gui;
|
||||||
|
|
||||||
defer self.resetAllButtons();
|
defer self.resetAllButtons();
|
||||||
|
|
||||||
const raycast_origin_sv = self.position_sv
|
const raycast_origin_sv = self.position_sv
|
||||||
@@ -294,7 +298,7 @@ pub fn update(self: *Player, dt: f32, chunks: *const Chunks) void {
|
|||||||
))
|
))
|
||||||
.round()
|
.round()
|
||||||
.asInt();
|
.asInt();
|
||||||
self.maybe_raycast_hit = chunks.raycast(raycast_origin_sv, raycast_ray_sv);
|
self.maybe_raycast_hit = game.chunks.raycast(raycast_origin_sv, raycast_ray_sv);
|
||||||
|
|
||||||
// --- GATHER INPUTS -------------------------------------------------------
|
// --- GATHER INPUTS -------------------------------------------------------
|
||||||
|
|
||||||
@@ -343,7 +347,7 @@ pub fn update(self: *Player, dt: f32, chunks: *const Chunks) void {
|
|||||||
var max_sv = position_sv.add(.init(collision_half_width_sv, collision_half_width_sv, collision_height_sv));
|
var max_sv = position_sv.add(.init(collision_half_width_sv, collision_half_width_sv, collision_height_sv));
|
||||||
|
|
||||||
if (vertical_displacement_sv > 0) {
|
if (vertical_displacement_sv > 0) {
|
||||||
if (chunks.sweepCastUp(min_sv, max_sv, vertical_displacement_sv)) |hit| {
|
if (game.chunks.sweepCastUp(min_sv, max_sv, vertical_displacement_sv)) |hit| {
|
||||||
vertical_displacement_sv -= hit.projected_distance_sv;
|
vertical_displacement_sv -= hit.projected_distance_sv;
|
||||||
}
|
}
|
||||||
position_sv = .add(position_sv, .init(0, 0, vertical_displacement_sv));
|
position_sv = .add(position_sv, .init(0, 0, vertical_displacement_sv));
|
||||||
@@ -353,7 +357,7 @@ pub fn update(self: *Player, dt: f32, chunks: *const Chunks) void {
|
|||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < 2) : (i += 1) {
|
while (i < 2) : (i += 1) {
|
||||||
if (chunks.sweepCastHorizontal(min_sv, max_sv, horizontal_displacement_sv)) |hit| {
|
if (game.chunks.sweepCastHorizontal(min_sv, max_sv, horizontal_displacement_sv)) |hit| {
|
||||||
const adjustment = math.mulFrac2(hit.normal_frac.dropZ(), hit.projected_distance_sv);
|
const adjustment = math.mulFrac2(hit.normal_frac.dropZ(), hit.projected_distance_sv);
|
||||||
horizontal_displacement_sv = .add(
|
horizontal_displacement_sv = .add(
|
||||||
horizontal_displacement_sv,
|
horizontal_displacement_sv,
|
||||||
@@ -368,7 +372,7 @@ pub fn update(self: *Player, dt: f32, chunks: *const Chunks) void {
|
|||||||
if (vertical_displacement_sv < 0.0) {
|
if (vertical_displacement_sv < 0.0) {
|
||||||
min_sv = .add(min_sv, horizontal_displacement_sv.withZ(0));
|
min_sv = .add(min_sv, horizontal_displacement_sv.withZ(0));
|
||||||
max_sv = .add(max_sv, horizontal_displacement_sv.withZ(0));
|
max_sv = .add(max_sv, horizontal_displacement_sv.withZ(0));
|
||||||
if (chunks.sweepCastDown(min_sv, max_sv, -vertical_displacement_sv)) |hit| {
|
if (game.chunks.sweepCastDown(min_sv, max_sv, -vertical_displacement_sv)) |hit| {
|
||||||
vertical_displacement_sv += hit.projected_distance_sv;
|
vertical_displacement_sv += hit.projected_distance_sv;
|
||||||
}
|
}
|
||||||
position_sv = .add(position_sv, .init(0, 0, vertical_displacement_sv));
|
position_sv = .add(position_sv, .init(0, 0, vertical_displacement_sv));
|
||||||
@@ -377,6 +381,149 @@ pub fn update(self: *Player, dt: f32, chunks: *const Chunks) void {
|
|||||||
self.position_sv = position_sv;
|
self.position_sv = position_sv;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- GUI -----------------------------------------------------------------
|
||||||
|
|
||||||
|
const extent = swapchain.extent;
|
||||||
|
const framebuffer_size = vm.Vector2.init(
|
||||||
|
@floatFromInt(extent.width),
|
||||||
|
@floatFromInt(extent.height),
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- CROSSHAIR ---
|
||||||
|
|
||||||
|
{
|
||||||
|
const crosshair_half_extent_px = 8;
|
||||||
|
const crosshair_half_width_px = 1;
|
||||||
|
|
||||||
|
gui.pushBox(.{
|
||||||
|
.background_color = .init(1, 1, 1, 0.5),
|
||||||
|
.border_color = .init(0, 0, 0, 1),
|
||||||
|
.position_sspx = framebuffer_size.mulScalar(0.5).add(.init(-crosshair_half_extent_px, -crosshair_half_width_px)),
|
||||||
|
.size_px = .init(2.0 * crosshair_half_extent_px, 2.0 * crosshair_half_width_px),
|
||||||
|
.border_width_px = 0,
|
||||||
|
.border_radius_px = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
gui.pushBox(.{
|
||||||
|
.background_color = .init(1, 1, 1, 0.5),
|
||||||
|
.border_color = .init(0, 0, 0, 1),
|
||||||
|
.position_sspx = framebuffer_size.mulScalar(0.5).add(.init(-crosshair_half_width_px, -crosshair_half_extent_px)),
|
||||||
|
.size_px = .init(2.0 * crosshair_half_width_px, 2.0 * crosshair_half_extent_px),
|
||||||
|
.border_width_px = 0,
|
||||||
|
.border_radius_px = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- COORDINATES ---
|
||||||
|
|
||||||
|
{
|
||||||
|
const margin: f16 = 8;
|
||||||
|
const font_size: f32 = 16;
|
||||||
|
|
||||||
|
// NOTE We have to do the number formatting semi-manually, because zig's
|
||||||
|
// std fmt is weird with signed numbers
|
||||||
|
|
||||||
|
var number_buf: [9]u8 = undefined; // sign ('-' or ' ') and 8 hex digits
|
||||||
|
|
||||||
|
gui.pushBox(.{
|
||||||
|
.background_color = .init(0, 0, 0, 0.75),
|
||||||
|
.border_color = .init(0, 0, 0, 1),
|
||||||
|
.position_sspx = .init(0, 0),
|
||||||
|
.size_px = .init(164, 4 * margin + 3 * font_size),
|
||||||
|
.border_width_px = 0,
|
||||||
|
.border_radius_px = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
gui.printLine("X {s}", .{formatHexCoord(self.position_sv.x, &number_buf)}, .{
|
||||||
|
.position_sspx = .init(margin, margin + font_size),
|
||||||
|
.font_id = @enumFromInt(0),
|
||||||
|
.cap_height_px = font_size,
|
||||||
|
.color = .init(1, 1, 1, 1),
|
||||||
|
});
|
||||||
|
|
||||||
|
gui.printLine("Y {s}", .{formatHexCoord(self.position_sv.y, &number_buf)}, .{
|
||||||
|
.position_sspx = .init(margin, 2 * (margin + font_size)),
|
||||||
|
.font_id = @enumFromInt(0),
|
||||||
|
.cap_height_px = font_size,
|
||||||
|
.color = .init(1, 1, 1, 1),
|
||||||
|
});
|
||||||
|
|
||||||
|
gui.printLine("Z {s}", .{formatHexCoord(self.position_sv.z, &number_buf)}, .{
|
||||||
|
.position_sspx = .init(margin, 3 * (margin + font_size)),
|
||||||
|
.font_id = @enumFromInt(0),
|
||||||
|
.cap_height_px = font_size,
|
||||||
|
.color = .init(1, 1, 1, 1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- HOTBAR ---
|
||||||
|
|
||||||
|
{
|
||||||
|
const block_selection_outline_width_px: f32 = 4;
|
||||||
|
|
||||||
|
const block_image_size_px: f32 = 48;
|
||||||
|
const block_image_gap_px: f32 = 8;
|
||||||
|
const block_image_margin_px: f32 = 8;
|
||||||
|
|
||||||
|
const hotbar_width_px: f32 = @as(f32, @floatFromInt(blocks.len)) * block_image_size_px + @as(f32, @floatFromInt(blocks.len - 1)) * block_image_gap_px;
|
||||||
|
const hotbar_left_px: f32 = 0.5 * (framebuffer_size.x - hotbar_width_px);
|
||||||
|
const hotbar_top_px: f32 = framebuffer_size.y - block_image_margin_px - block_image_size_px;
|
||||||
|
|
||||||
|
gui.pushBox(.{
|
||||||
|
.background_color = .init(0, 0, 0, 0.75),
|
||||||
|
.border_color = .init(0, 0, 0, 1),
|
||||||
|
.position_sspx = .init(hotbar_left_px - block_image_margin_px, hotbar_top_px - block_image_margin_px),
|
||||||
|
.size_px = .init(hotbar_width_px + 2 * block_image_margin_px, block_image_size_px + 2 * block_image_margin_px),
|
||||||
|
.border_width_px = 4,
|
||||||
|
.border_radius_px = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
var cx: f32 = hotbar_left_px;
|
||||||
|
for (blocks, 0..) |filename, i| {
|
||||||
|
defer cx += block_image_size_px + block_image_gap_px;
|
||||||
|
|
||||||
|
const id = game.blocks.getOrLoad(filename) catch |err| {
|
||||||
|
std.log.err("Error while rendering hotbar block {s}: {}", .{ filename, err });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
const definition = &game.blocks.array.items[@intFromEnum(id)];
|
||||||
|
|
||||||
|
const material_id = definition.walls.positive_z.material;
|
||||||
|
const material = &materials.array.items[@intFromEnum(material_id)];
|
||||||
|
|
||||||
|
if (i == self.block_index) {
|
||||||
|
gui.pushBox(.{
|
||||||
|
.background_color = .initScalar(0),
|
||||||
|
.border_color = .init(1, 1, 1, 1),
|
||||||
|
.position_sspx = vm.Vector2.init(cx, hotbar_top_px).sub(.initScalar(block_selection_outline_width_px)),
|
||||||
|
.size_px = .initScalar(block_image_size_px + 2 * block_selection_outline_width_px),
|
||||||
|
.border_width_px = block_selection_outline_width_px,
|
||||||
|
.border_radius_px = block_selection_outline_width_px,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const texture_id = material.base_color_texture;
|
||||||
|
gui.pushImage(.{
|
||||||
|
.position_sspx = .init(cx, hotbar_top_px),
|
||||||
|
.size_px = .initScalar(block_image_size_px),
|
||||||
|
.texture_id = texture_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn formatHexCoord(coord: i32, buf: *[9]u8) []const u8 {
|
||||||
|
buf[0] = ' ';
|
||||||
|
const coord_abs = @abs(coord);
|
||||||
|
_ = std.fmt.bufPrint(buf[1..9], "{X:>8}", .{coord_abs}) catch unreachable;
|
||||||
|
|
||||||
|
if (coord < 0) {
|
||||||
|
const i = std.mem.findScalarLast(u8, buf, ' ') orelse unreachable;
|
||||||
|
buf[i] = '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf[0..];
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resetAllButtons(self: *Player) void {
|
fn resetAllButtons(self: *Player) void {
|
||||||
|
|||||||
@@ -272,9 +272,9 @@ pub fn init() !*Engine {
|
|||||||
|
|
||||||
var enabled_features_vulkan12: vk.PhysicalDeviceVulkan12Features = .{
|
var enabled_features_vulkan12: vk.PhysicalDeviceVulkan12Features = .{
|
||||||
.p_next = &enabled_features_vulkan11,
|
.p_next = &enabled_features_vulkan11,
|
||||||
|
.shader_float_16 = .true,
|
||||||
.descriptor_indexing = .true,
|
.descriptor_indexing = .true,
|
||||||
.descriptor_binding_partially_bound = .true,
|
.descriptor_binding_partially_bound = .true,
|
||||||
.descriptor_binding_variable_descriptor_count = .true,
|
|
||||||
.runtime_descriptor_array = .true,
|
.runtime_descriptor_array = .true,
|
||||||
.scalar_block_layout = .true,
|
.scalar_block_layout = .true,
|
||||||
.imageless_framebuffer = .true,
|
.imageless_framebuffer = .true,
|
||||||
@@ -744,13 +744,11 @@ pub const DescriptorPoolCreateInfo = struct {
|
|||||||
pub const DescriptorSetAllocateInfo = struct {
|
pub const DescriptorSetAllocateInfo = struct {
|
||||||
descriptor_pool: vk.DescriptorPool,
|
descriptor_pool: vk.DescriptorPool,
|
||||||
set_layout: vk.DescriptorSetLayout,
|
set_layout: vk.DescriptorSetLayout,
|
||||||
variable_descriptor_count: ?u32 = null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const DescriptorSetsAllocateInfo = struct {
|
pub const DescriptorSetsAllocateInfo = struct {
|
||||||
descriptor_pool: vk.DescriptorPool,
|
descriptor_pool: vk.DescriptorPool,
|
||||||
set_layouts: []const vk.DescriptorSetLayout,
|
set_layouts: []const vk.DescriptorSetLayout,
|
||||||
variable_descriptor_counts: []const u32 = &.{},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const DescriptorSetLayoutBinding = struct {
|
pub const DescriptorSetLayoutBinding = struct {
|
||||||
@@ -968,28 +966,13 @@ pub fn allocateDescriptorSet(self: *Engine, allocate_info: DescriptorSetAllocate
|
|||||||
try self.allocateDescriptorSets(.{
|
try self.allocateDescriptorSets(.{
|
||||||
.descriptor_pool = allocate_info.descriptor_pool,
|
.descriptor_pool = allocate_info.descriptor_pool,
|
||||||
.set_layouts = &.{allocate_info.set_layout},
|
.set_layouts = &.{allocate_info.set_layout},
|
||||||
.variable_descriptor_counts = if (allocate_info.variable_descriptor_count) |x| &.{x} else &.{},
|
|
||||||
}, &descriptor_sets);
|
}, &descriptor_sets);
|
||||||
return descriptor_sets[0];
|
return descriptor_sets[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocateDescriptorSets(self: *Engine, allocate_info: DescriptorSetsAllocateInfo, descriptor_sets: []vk.DescriptorSet) !void {
|
pub fn allocateDescriptorSets(self: *Engine, allocate_info: DescriptorSetsAllocateInfo, descriptor_sets: []vk.DescriptorSet) !void {
|
||||||
std.debug.assert(descriptor_sets.len >= allocate_info.set_layouts.len);
|
std.debug.assert(descriptor_sets.len >= allocate_info.set_layouts.len);
|
||||||
|
|
||||||
const has_variable_descriptor_counts = allocate_info.variable_descriptor_counts.len > 0;
|
|
||||||
|
|
||||||
var p_next: ?*const anyopaque = null;
|
|
||||||
|
|
||||||
if (has_variable_descriptor_counts) {
|
|
||||||
p_next = &vk.DescriptorSetVariableDescriptorCountAllocateInfo{
|
|
||||||
.p_next = p_next,
|
|
||||||
.descriptor_set_count = @intCast(allocate_info.variable_descriptor_counts.len),
|
|
||||||
.p_descriptor_counts = allocate_info.variable_descriptor_counts.ptr,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.device.allocateDescriptorSets(&.{
|
try self.device.allocateDescriptorSets(&.{
|
||||||
.p_next = p_next,
|
|
||||||
.descriptor_pool = allocate_info.descriptor_pool,
|
.descriptor_pool = allocate_info.descriptor_pool,
|
||||||
.descriptor_set_count = @intCast(allocate_info.set_layouts.len),
|
.descriptor_set_count = @intCast(allocate_info.set_layouts.len),
|
||||||
.p_set_layouts = allocate_info.set_layouts.ptr,
|
.p_set_layouts = allocate_info.set_layouts.ptr,
|
||||||
|
|||||||
452
src/engine/Font.zig
Normal file
452
src/engine/Font.zig
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
const Font = @This();
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const ctx = @import("../AppContext.zig");
|
||||||
|
const shaders = @import("../shaders.zig");
|
||||||
|
const vm = @import("vecmath");
|
||||||
|
|
||||||
|
const GenericBuffer = @import("GenericBuffer.zig").GenericBuffer;
|
||||||
|
|
||||||
|
pub const CodePointRange = struct {
|
||||||
|
/// inclusive
|
||||||
|
start: u21,
|
||||||
|
/// exclusive
|
||||||
|
end: u21,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const InitInfo = struct {
|
||||||
|
codepoint_ranges: []const CodePointRange = &.{
|
||||||
|
.{ .start = 32, .end = 127 },
|
||||||
|
},
|
||||||
|
/// [vertical, horizontal]
|
||||||
|
bands_per_em: vm.Vector2 = .init(8, 8),
|
||||||
|
name: ?[]const u8 = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const GlyphCpuData = struct {
|
||||||
|
/// `undefined` when `gpu_glyph_id == null`
|
||||||
|
bottom_left_em: vm.Vector2,
|
||||||
|
advance_em: f32,
|
||||||
|
left_side_bearing_em: f32,
|
||||||
|
/// If `null`, the glyph is empty (like a space). In such case, we only need
|
||||||
|
/// to advance the cursor by given `advance_em` and we must not emit any gpu
|
||||||
|
/// glyph instances.
|
||||||
|
gpu_glyph_id: ?u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
cpu_glyph_data: []GlyphCpuData,
|
||||||
|
codepoint_glyph_id: std.hash_map.AutoHashMapUnmanaged(u21, u16),
|
||||||
|
kerning_em: std.hash_map.AutoHashMapUnmanaged([2]u16, f32),
|
||||||
|
cap_height_em: f32,
|
||||||
|
|
||||||
|
glyph_data: GenericBuffer(shaders.GlyphData),
|
||||||
|
control_points: GenericBuffer([2]f16),
|
||||||
|
band_data: GenericBuffer(u32),
|
||||||
|
|
||||||
|
const BandHelper = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
bands: []std.ArrayList(u32),
|
||||||
|
|
||||||
|
fn init(allocator: std.mem.Allocator) BandHelper {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.bands = &.{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensureBandCount(self: *BandHelper, desired_band_count: usize) !void {
|
||||||
|
std.debug.assert(desired_band_count > 0);
|
||||||
|
|
||||||
|
const old_band_count = self.bands.len;
|
||||||
|
if (old_band_count >= desired_band_count) return;
|
||||||
|
|
||||||
|
const new_band_count = std.math.ceilPowerOfTwo(usize, desired_band_count) catch |err| switch (err) {
|
||||||
|
error.Overflow => return error.OutOfMemory,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.bands = try self.allocator.realloc(self.bands, new_band_count);
|
||||||
|
for (old_band_count..new_band_count) |i| {
|
||||||
|
self.bands[i] = .empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clearBands(self: *BandHelper, band_count: usize) void {
|
||||||
|
for (self.bands[0..band_count]) |*array| {
|
||||||
|
array.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insertControlPointPtr(self: *BandHelper, band: usize, ptr: u32) !void {
|
||||||
|
const array = &self.bands[band];
|
||||||
|
try array.append(self.allocator, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sortBands(self: *BandHelper, coordinate: u1, band_count: usize, control_points: []const [2]f16) void {
|
||||||
|
for (self.bands[0..band_count]) |*array| {
|
||||||
|
const sort_ctx = SortContext{
|
||||||
|
.coordinate = coordinate,
|
||||||
|
.control_points = control_points,
|
||||||
|
};
|
||||||
|
|
||||||
|
std.mem.sortUnstable(u32, array.items, &sort_ctx, SortContext.lessThan);
|
||||||
|
|
||||||
|
// std.debug.print(" BAND {d} sorted by coordinate {d}\n", .{ array - self.bands.ptr, coordinate });
|
||||||
|
// for (array.items) |cp_ptr| {
|
||||||
|
// const cp0 = control_points[cp_ptr + 0];
|
||||||
|
// const cp1 = control_points[cp_ptr + 1];
|
||||||
|
// const cp2 = control_points[cp_ptr + 2];
|
||||||
|
// const max = @max(cp0[coordinate], cp1[coordinate], cp2[coordinate]);
|
||||||
|
// std.debug.print(" max {d} | ({d}, {d}) → ({d}, {d}) → ({d}, {d})\n", .{ max, cp0[0], cp0[1], cp1[0], cp1[1], cp2[0], cp2[1] });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(self: *const BandHelper, band_count: usize, buffer: *std.ArrayList(u32), buffer_allocator: std.mem.Allocator) !void {
|
||||||
|
var band_data_ptr = buffer.items.len;
|
||||||
|
|
||||||
|
// --- HEADERS ---
|
||||||
|
|
||||||
|
band_data_ptr += band_count;
|
||||||
|
|
||||||
|
for (self.bands[0..band_count]) |*array| {
|
||||||
|
const band = array.items;
|
||||||
|
|
||||||
|
// Header is a self-relative forward-only pointer in high 16 bits
|
||||||
|
// and length in low 16 bits.
|
||||||
|
|
||||||
|
const ptr = std.math.cast(u16, band_data_ptr - buffer.items.len) orelse return error.Overflow;
|
||||||
|
const len = std.math.cast(u16, band.len) orelse return error.Overflow;
|
||||||
|
|
||||||
|
try buffer.append(buffer_allocator, (@as(u32, ptr) << 16) | len);
|
||||||
|
|
||||||
|
band_data_ptr += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- BAND DATA ---
|
||||||
|
|
||||||
|
for (self.bands[0..band_count]) |*array| {
|
||||||
|
const band = array.items;
|
||||||
|
try buffer.appendSlice(buffer_allocator, band);
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.assert(band_data_ptr == buffer.items.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: *BandHelper) void {
|
||||||
|
for (self.bands) |*array| {
|
||||||
|
array.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.allocator.free(self.bands);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SortContext = struct {
|
||||||
|
coordinate: u1,
|
||||||
|
control_points: []const [2]f16,
|
||||||
|
|
||||||
|
fn lessThan(self: *const SortContext, lhs: u32, rhs: u32) bool {
|
||||||
|
const lhs_cp0 = self.control_points[lhs + 0];
|
||||||
|
const lhs_cp1 = self.control_points[lhs + 1];
|
||||||
|
const lhs_cp2 = self.control_points[lhs + 2];
|
||||||
|
|
||||||
|
const lhs_max = @max(
|
||||||
|
lhs_cp0[self.coordinate],
|
||||||
|
lhs_cp1[self.coordinate],
|
||||||
|
lhs_cp2[self.coordinate],
|
||||||
|
);
|
||||||
|
|
||||||
|
const rhs_cp0 = self.control_points[rhs + 0];
|
||||||
|
const rhs_cp1 = self.control_points[rhs + 1];
|
||||||
|
const rhs_cp2 = self.control_points[rhs + 2];
|
||||||
|
|
||||||
|
const rhs_max = @max(
|
||||||
|
rhs_cp0[self.coordinate],
|
||||||
|
rhs_cp1[self.coordinate],
|
||||||
|
rhs_cp2[self.coordinate],
|
||||||
|
);
|
||||||
|
|
||||||
|
return lhs_max >= rhs_max;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(ttf_buffer: []const u8, init_info: InitInfo) !Font {
|
||||||
|
const allocator_general = ctx.allocator_general;
|
||||||
|
const allocator_frame = ctx.allocator_frame;
|
||||||
|
const stbtt = ctx.stbtt;
|
||||||
|
|
||||||
|
const ttf_font = try stbtt.initFont(ttf_buffer);
|
||||||
|
defer ttf_font.deinit();
|
||||||
|
|
||||||
|
const units_per_em = ttf_font.unitsPerEm();
|
||||||
|
const em_per_unit = 1 / @as(f32, @floatFromInt(units_per_em));
|
||||||
|
|
||||||
|
// std.debug.print("Units per EM: {d}\nEM per unit: {d}\n", .{ units_per_em, em_per_unit });
|
||||||
|
|
||||||
|
var glyph_count: u16 = 0;
|
||||||
|
for (init_info.codepoint_ranges) |range| {
|
||||||
|
const range_size = std.math.cast(u16, range.end - range.start) orelse return error.TooManyGlyphs;
|
||||||
|
glyph_count = std.math.add(u16, glyph_count, range_size) catch |err| switch (err) {
|
||||||
|
error.Overflow => return error.TooManyGlyphs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var codepoint_glyph_id: std.hash_map.AutoHashMapUnmanaged(u21, u16) = .empty;
|
||||||
|
errdefer codepoint_glyph_id.deinit(allocator_general);
|
||||||
|
try codepoint_glyph_id.ensureTotalCapacity(allocator_general, glyph_count);
|
||||||
|
|
||||||
|
var external_glyph_id_to_cpu_glyph_id: std.hash_map.AutoHashMapUnmanaged(u16, u16) = .empty;
|
||||||
|
try external_glyph_id_to_cpu_glyph_id.ensureTotalCapacity(allocator_frame, glyph_count);
|
||||||
|
|
||||||
|
var kerning_em: std.hash_map.AutoHashMapUnmanaged([2]u16, f32) = .empty;
|
||||||
|
errdefer kerning_em.deinit(allocator_general);
|
||||||
|
|
||||||
|
const cap_height = ttf_font.getCapHeight() orelse return error.FontError;
|
||||||
|
const cap_height_em = @as(f32, @floatFromInt(cap_height)) * em_per_unit;
|
||||||
|
|
||||||
|
// std.debug.print("Cap height: {d} U | {d} EM\n", .{ cap_height, cap_height_em });
|
||||||
|
|
||||||
|
var cpu_glyph_data: std.ArrayList(GlyphCpuData) = try .initCapacity(allocator_general, glyph_count);
|
||||||
|
errdefer cpu_glyph_data.deinit(allocator_general);
|
||||||
|
|
||||||
|
var gpu_glyph_data: std.ArrayList(shaders.GlyphData) = try .initCapacity(allocator_frame, glyph_count);
|
||||||
|
|
||||||
|
var control_points_array: std.ArrayList([2]f16) = .empty;
|
||||||
|
defer control_points_array.deinit(allocator_general);
|
||||||
|
|
||||||
|
var band_data_array: std.ArrayList(u32) = .empty;
|
||||||
|
defer band_data_array.deinit(allocator_general);
|
||||||
|
|
||||||
|
var vertical_band_helper: BandHelper = .init(allocator_general);
|
||||||
|
defer vertical_band_helper.deinit();
|
||||||
|
|
||||||
|
var horizontal_band_helper: BandHelper = .init(allocator_general);
|
||||||
|
defer horizontal_band_helper.deinit();
|
||||||
|
|
||||||
|
for (init_info.codepoint_ranges) |range| {
|
||||||
|
var codepoint: u21 = range.start;
|
||||||
|
while (codepoint < range.end) : (codepoint += 1) {
|
||||||
|
// std.debug.print("Processing codepoint 0x{X} '{u}'\n", .{ codepoint, codepoint });
|
||||||
|
if (ttf_font.findGlyphIndex(codepoint)) |external_glyph_id| {
|
||||||
|
const external_glyph_id_to_cpu_glyph_id_get_or_put_result = external_glyph_id_to_cpu_glyph_id.getOrPutAssumeCapacity(external_glyph_id);
|
||||||
|
if (external_glyph_id_to_cpu_glyph_id_get_or_put_result.found_existing) {
|
||||||
|
codepoint_glyph_id.putAssumeCapacityNoClobber(codepoint, external_glyph_id_to_cpu_glyph_id_get_or_put_result.value_ptr.*);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
const cpu_glyph_id = @as(u16, @intCast(cpu_glyph_data.items.len));
|
||||||
|
external_glyph_id_to_cpu_glyph_id_get_or_put_result.value_ptr.* = cpu_glyph_id;
|
||||||
|
codepoint_glyph_id.putAssumeCapacityNoClobber(codepoint, cpu_glyph_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const glyph_metrics = ttf_font.getGlyphMetrics(external_glyph_id);
|
||||||
|
// std.debug.print(" Advance: {d} U\n", .{glyph_metrics.advance_width});
|
||||||
|
// std.debug.print(" LSB: {d} U\n", .{glyph_metrics.left_side_bearing});
|
||||||
|
|
||||||
|
if (ttf_font.isGlyphEmpty(external_glyph_id)) {
|
||||||
|
// std.debug.print(" Glyph is empty\n", .{});
|
||||||
|
cpu_glyph_data.appendAssumeCapacity(.{
|
||||||
|
.bottom_left_em = undefined,
|
||||||
|
.left_side_bearing_em = @as(f32, @floatFromInt(glyph_metrics.left_side_bearing)) * em_per_unit,
|
||||||
|
.advance_em = @as(f32, @floatFromInt(glyph_metrics.advance_width)) * em_per_unit,
|
||||||
|
.gpu_glyph_id = null,
|
||||||
|
});
|
||||||
|
} else if (ttf_font.getGlyphBox(external_glyph_id)) |box| {
|
||||||
|
const bottom_left_em = vm.Vector2.init(
|
||||||
|
@floatFromInt(box.x_min),
|
||||||
|
@floatFromInt(box.y_min),
|
||||||
|
).mulScalar(em_per_unit);
|
||||||
|
const size_em = vm.Vector2.init(
|
||||||
|
@as(f32, @floatFromInt(box.x_max)) - @as(f32, @floatFromInt(box.x_min)),
|
||||||
|
@as(f32, @floatFromInt(box.y_max)) - @as(f32, @floatFromInt(box.y_min)),
|
||||||
|
).mulScalar(em_per_unit);
|
||||||
|
|
||||||
|
const gpu_glyph_id = @as(u16, @intCast(gpu_glyph_data.items.len));
|
||||||
|
cpu_glyph_data.appendAssumeCapacity(.{
|
||||||
|
.bottom_left_em = bottom_left_em,
|
||||||
|
.left_side_bearing_em = @as(f32, @floatFromInt(glyph_metrics.left_side_bearing)) * em_per_unit,
|
||||||
|
.advance_em = @as(f32, @floatFromInt(glyph_metrics.advance_width)) * em_per_unit,
|
||||||
|
.gpu_glyph_id = gpu_glyph_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const band_count = vm.Vector2.max(.round(.mul(size_em, init_info.bands_per_em)), .initScalar(1));
|
||||||
|
|
||||||
|
const vertical_band_count: u32 = @trunc(band_count.x);
|
||||||
|
const horizontal_band_count: u32 = @trunc(band_count.y);
|
||||||
|
|
||||||
|
try vertical_band_helper.ensureBandCount(vertical_band_count);
|
||||||
|
try horizontal_band_helper.ensureBandCount(horizontal_band_count);
|
||||||
|
|
||||||
|
vertical_band_helper.clearBands(vertical_band_count);
|
||||||
|
horizontal_band_helper.clearBands(horizontal_band_count);
|
||||||
|
|
||||||
|
const vertices = ttf_font.getGlyphShape(external_glyph_id);
|
||||||
|
defer ttf_font.freeShape(vertices);
|
||||||
|
|
||||||
|
// std.debug.print(" bottom left: {f} [EM]\n", .{bottom_left_em});
|
||||||
|
// std.debug.print(" size: {f} [EM]\n", .{size_em});
|
||||||
|
// std.debug.print(" band count: {f}\n", .{band_count});
|
||||||
|
// std.debug.print(" vertices: {d}\n", .{vertices.len});
|
||||||
|
|
||||||
|
var cp0: vm.Vector2 = undefined;
|
||||||
|
for (vertices) |vertex| {
|
||||||
|
var cp1: vm.Vector2 = undefined;
|
||||||
|
var cp2: vm.Vector2 = undefined;
|
||||||
|
switch (vertex.type) {
|
||||||
|
.STBTT_vmove => {
|
||||||
|
cp0.x = @as(f32, @floatFromInt(vertex.x)) * em_per_unit;
|
||||||
|
cp0.y = @as(f32, @floatFromInt(vertex.y)) * em_per_unit;
|
||||||
|
try control_points_array.append(
|
||||||
|
allocator_general,
|
||||||
|
.{ @floatCast(cp0.x), @floatCast(cp0.y) },
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
.STBTT_vline => {
|
||||||
|
cp1.x = @as(f32, @floatFromInt(vertex.x)) * em_per_unit;
|
||||||
|
cp1.y = @as(f32, @floatFromInt(vertex.y)) * em_per_unit;
|
||||||
|
cp2 = cp1;
|
||||||
|
try control_points_array.appendNTimes(
|
||||||
|
allocator_general,
|
||||||
|
.{ @floatCast(cp1.x), @floatCast(cp1.y) },
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
.STBTT_vcurve => {
|
||||||
|
cp1.x = @as(f32, @floatFromInt(vertex.cx)) * em_per_unit;
|
||||||
|
cp1.y = @as(f32, @floatFromInt(vertex.cy)) * em_per_unit;
|
||||||
|
cp2.x = @as(f32, @floatFromInt(vertex.x)) * em_per_unit;
|
||||||
|
cp2.y = @as(f32, @floatFromInt(vertex.y)) * em_per_unit;
|
||||||
|
try control_points_array.append(allocator_general, .{ @floatCast(cp1.x), @floatCast(cp1.y) });
|
||||||
|
try control_points_array.append(allocator_general, .{ @floatCast(cp2.x), @floatCast(cp2.y) });
|
||||||
|
},
|
||||||
|
.STBTT_vcubic => unreachable,
|
||||||
|
}
|
||||||
|
defer cp0 = cp2;
|
||||||
|
|
||||||
|
const cp_min = cp0.min(cp1).min(cp2).sub(.initScalar(em_per_unit));
|
||||||
|
const cp_max = cp0.max(cp1).max(cp2).add(.initScalar(em_per_unit));
|
||||||
|
|
||||||
|
const min_band_f = band_count.mul(.sub(cp_min, bottom_left_em)).div(size_em)
|
||||||
|
.max(.initScalar(0))
|
||||||
|
.min(.sub(band_count, .initScalar(1)))
|
||||||
|
.floor();
|
||||||
|
const max_band_f = band_count.mul(.sub(cp_max, bottom_left_em)).div(size_em)
|
||||||
|
.max(.initScalar(0))
|
||||||
|
.min(.sub(band_count, .initScalar(1)))
|
||||||
|
.floor();
|
||||||
|
|
||||||
|
// std.debug.print(" {f} → {f} → {f} | bands: {f} → {f}\n", .{ cp0, cp1, cp2, min_band_f, max_band_f });
|
||||||
|
|
||||||
|
const vertical_band_start = @as(u32, @trunc(min_band_f.x));
|
||||||
|
const vertical_band_end = @as(u32, @trunc(max_band_f.x)) + 1;
|
||||||
|
const horizontal_band_start = @as(u32, @trunc(min_band_f.y));
|
||||||
|
const horizontal_band_end = @as(u32, @trunc(max_band_f.y)) + 1;
|
||||||
|
|
||||||
|
const cp_ptr = @as(u32, @intCast(control_points_array.items.len - 3));
|
||||||
|
|
||||||
|
for (vertical_band_start..vertical_band_end) |vband| {
|
||||||
|
try vertical_band_helper.insertControlPointPtr(vband, cp_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (horizontal_band_start..horizontal_band_end) |hband| {
|
||||||
|
try horizontal_band_helper.insertControlPointPtr(hband, cp_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
cp0 = cp2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertical_band_ptr = @as(u32, @intCast(band_data_array.items.len));
|
||||||
|
vertical_band_helper.sortBands(1, vertical_band_count, control_points_array.items);
|
||||||
|
try vertical_band_helper.write(vertical_band_count, &band_data_array, allocator_general);
|
||||||
|
|
||||||
|
const horizontal_band_ptr = @as(u32, @intCast(band_data_array.items.len));
|
||||||
|
horizontal_band_helper.sortBands(0, horizontal_band_count, control_points_array.items);
|
||||||
|
try horizontal_band_helper.write(horizontal_band_count, &band_data_array, allocator_general);
|
||||||
|
|
||||||
|
// std.debug.print(" vband: [{d}] @ {d}\n hband: [{d}] @ {d}\n", .{ vertical_band_count, vertical_band_ptr, horizontal_band_count, horizontal_band_ptr });
|
||||||
|
|
||||||
|
gpu_glyph_data.appendAssumeCapacity(.{
|
||||||
|
.bottomLeftEM = bottom_left_em,
|
||||||
|
.sizeEM = size_em,
|
||||||
|
.verticalBandPtr = vertical_band_ptr,
|
||||||
|
.horizontalBandPtr = horizontal_band_ptr,
|
||||||
|
.verticalBandCount = vertical_band_count,
|
||||||
|
.horizontalBandCount = horizontal_band_count,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return error.FontError;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return error.MissingGlyph;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var kerning_it = ttf_font.kerningTableIterator();
|
||||||
|
while (kerning_it.next()) |entry| {
|
||||||
|
if (external_glyph_id_to_cpu_glyph_id.contains(entry.glyph_prev) and external_glyph_id_to_cpu_glyph_id.contains(entry.glyph_next)) {
|
||||||
|
const advance_em = @as(f32, @floatFromInt(entry.advance)) * em_per_unit;
|
||||||
|
try kerning_em.put(allocator_general, .{ entry.glyph_prev, entry.glyph_next }, advance_em);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// std.debug.print("Kerning table size: {d}\n", .{kerning_em.size});
|
||||||
|
|
||||||
|
var glyph_data: GenericBuffer(shaders.GlyphData) = try .init(.{
|
||||||
|
.usage = .storage,
|
||||||
|
.target_queue = .graphics,
|
||||||
|
.capacity = @intCast(gpu_glyph_data.items.len),
|
||||||
|
.name = "Glyph data",
|
||||||
|
});
|
||||||
|
errdefer glyph_data.deinit();
|
||||||
|
|
||||||
|
// std.debug.print("Glyph data size: {d} [B]\n", .{gpu_glyph_data.items.len * @sizeOf(shaders.GlyphData)});
|
||||||
|
|
||||||
|
var control_points: GenericBuffer([2]f16) = try .init(.{
|
||||||
|
.usage = .storage,
|
||||||
|
.target_queue = .graphics,
|
||||||
|
.capacity = @intCast(control_points_array.items.len),
|
||||||
|
.name = "Control points",
|
||||||
|
});
|
||||||
|
errdefer control_points.deinit();
|
||||||
|
|
||||||
|
// std.debug.print("Control points size: {d} [B]\n", .{control_points_array.items.len * @sizeOf([2]f16)});
|
||||||
|
|
||||||
|
var band_data: GenericBuffer(u32) = try .init(.{
|
||||||
|
.usage = .storage,
|
||||||
|
.target_queue = .graphics,
|
||||||
|
.capacity = @intCast(band_data_array.items.len),
|
||||||
|
.name = "Band data",
|
||||||
|
});
|
||||||
|
errdefer band_data.deinit();
|
||||||
|
|
||||||
|
// std.debug.print("Band data size: {d} [B]\n", .{band_data_array.items.len * @sizeOf(u32)});
|
||||||
|
|
||||||
|
try glyph_data.writeSlice(gpu_glyph_data.items);
|
||||||
|
try control_points.writeSlice(control_points_array.items);
|
||||||
|
try band_data.writeSlice(band_data_array.items);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.cpu_glyph_data = cpu_glyph_data.toOwnedSliceAssert(),
|
||||||
|
.codepoint_glyph_id = codepoint_glyph_id,
|
||||||
|
.kerning_em = kerning_em,
|
||||||
|
.cap_height_em = cap_height_em,
|
||||||
|
|
||||||
|
.glyph_data = glyph_data,
|
||||||
|
.control_points = control_points,
|
||||||
|
.band_data = band_data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Font) void {
|
||||||
|
const allocator_general = ctx.allocator_general;
|
||||||
|
|
||||||
|
self.band_data.deinit();
|
||||||
|
self.control_points.deinit();
|
||||||
|
self.glyph_data.deinit();
|
||||||
|
|
||||||
|
self.codepoint_glyph_id.deinit(allocator_general);
|
||||||
|
allocator_general.free(self.cpu_glyph_data);
|
||||||
|
}
|
||||||
111
src/engine/Fonts.zig
Normal file
111
src/engine/Fonts.zig
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
const Fonts = @This();
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const ctx = @import("../AppContext.zig");
|
||||||
|
|
||||||
|
const Atom = @import("../engine/Atom.zig").Atom;
|
||||||
|
const Font = @import("Font.zig");
|
||||||
|
|
||||||
|
pub const Id = enum(u16) {
|
||||||
|
// VOLATILE Synchronize explicit values with `init` implementation.
|
||||||
|
|
||||||
|
_,
|
||||||
|
|
||||||
|
/// Cast an integer into an ID. This can produce an invalid ID.
|
||||||
|
pub fn fromInt(value: u16) Id {
|
||||||
|
return @enumFromInt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast an index into an ID. This can produce an invalid ID. The caller
|
||||||
|
/// asserts that the index is not greater than the max ID value.
|
||||||
|
pub fn fromIndex(index: usize) Id {
|
||||||
|
std.debug.assert(index < max_fonts);
|
||||||
|
return @enumFromInt(@as(u16, @intCast(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast an index into an ID. This can produce an invalid ID. Returns an
|
||||||
|
/// error if the index is greater than the max ID value.
|
||||||
|
pub fn fromIndexSafe(index: usize) error{Overflow}!Id {
|
||||||
|
if (index >= max_fonts) return error.Overflow;
|
||||||
|
return @enumFromInt(@as(u16, @intCast(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast an ID into an integer.
|
||||||
|
pub fn toInt(self: Id) u16 {
|
||||||
|
return @intFromEnum(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Stores all `Font` structs and maps a font ID to a `Font` struct.
|
||||||
|
/// Preallocated with `allocator_persistent`.
|
||||||
|
array: std.ArrayList(Font),
|
||||||
|
|
||||||
|
pub const max_fonts = 256;
|
||||||
|
|
||||||
|
pub fn init() !*Fonts {
|
||||||
|
const allocator_persistent = ctx.allocator_persistent;
|
||||||
|
|
||||||
|
const fonts = try allocator_persistent.create(Fonts);
|
||||||
|
|
||||||
|
fonts.* = .{
|
||||||
|
.array = .empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
for (fonts.array.items) |*font| {
|
||||||
|
font.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try fonts.array.ensureTotalCapacityPrecise(allocator_persistent, max_fonts);
|
||||||
|
|
||||||
|
// VOLATILE Synchronize with explicit values on top of `Id` type.
|
||||||
|
|
||||||
|
return fonts;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Fonts) void {
|
||||||
|
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
|
||||||
|
|
||||||
|
for (self.array.items) |*font| {
|
||||||
|
font.deinit();
|
||||||
|
}
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(
|
||||||
|
self: *Fonts,
|
||||||
|
filename: []const u8,
|
||||||
|
init_info: Font.InitInfo,
|
||||||
|
) !Id {
|
||||||
|
const id = Id.fromIndexSafe(self.array.items.len) catch |err| switch (err) {
|
||||||
|
error.Overflow => return error.OutOfFonts,
|
||||||
|
};
|
||||||
|
const font = try loadFont(filename, init_info);
|
||||||
|
|
||||||
|
self.array.appendAssumeCapacity(font);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadFont(
|
||||||
|
filename: []const u8,
|
||||||
|
init_info: Font.InitInfo,
|
||||||
|
) !Font {
|
||||||
|
const allocator_frame = ctx.allocator_frame;
|
||||||
|
const io = ctx.io;
|
||||||
|
|
||||||
|
std.log.debug("Loading font \"{s}\"...", .{filename});
|
||||||
|
|
||||||
|
const cwd = std.Io.Dir.cwd();
|
||||||
|
|
||||||
|
var dir = try cwd.openDir(io, "assets/fonts", .{});
|
||||||
|
defer dir.close(io);
|
||||||
|
|
||||||
|
const ttf_buffer = try dir.readFileAlloc(io, filename, allocator_frame, .unlimited);
|
||||||
|
|
||||||
|
var font = try Font.init(ttf_buffer, init_info);
|
||||||
|
errdefer font.deinit();
|
||||||
|
|
||||||
|
return font;
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ const vk = @import("vulkan");
|
|||||||
|
|
||||||
const CommandBuffer = @import("CommandBuffer.zig");
|
const CommandBuffer = @import("CommandBuffer.zig");
|
||||||
const Engine = @import("Engine.zig");
|
const Engine = @import("Engine.zig");
|
||||||
const StagingBuffer = @import("StagingBuffer.zig");
|
|
||||||
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
|
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
|
||||||
|
|
||||||
pub const Usage = enum {
|
pub const Usage = enum {
|
||||||
@@ -14,6 +13,7 @@ pub const Usage = enum {
|
|||||||
index,
|
index,
|
||||||
vertex,
|
vertex,
|
||||||
indirect,
|
indirect,
|
||||||
|
staging,
|
||||||
|
|
||||||
pub fn asBufferUsageFlags(self: Usage) vk.BufferUsageFlags {
|
pub fn asBufferUsageFlags(self: Usage) vk.BufferUsageFlags {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
@@ -22,6 +22,7 @@ pub const Usage = enum {
|
|||||||
.index => .{ .transfer_dst_bit = true, .index_buffer_bit = true },
|
.index => .{ .transfer_dst_bit = true, .index_buffer_bit = true },
|
||||||
.vertex => .{ .transfer_dst_bit = true, .vertex_buffer_bit = true },
|
.vertex => .{ .transfer_dst_bit = true, .vertex_buffer_bit = true },
|
||||||
.indirect => .{ .transfer_dst_bit = true, .indirect_buffer_bit = true },
|
.indirect => .{ .transfer_dst_bit = true, .indirect_buffer_bit = true },
|
||||||
|
.staging => .{ .transfer_src_bit = true },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -29,34 +30,27 @@ pub const Usage = enum {
|
|||||||
pub const InitInfo = struct {
|
pub const InitInfo = struct {
|
||||||
usage: Usage,
|
usage: Usage,
|
||||||
target_queue: TargetQueue,
|
target_queue: TargetQueue,
|
||||||
array_capacity: u32 = 0,
|
mappable: bool = false,
|
||||||
|
capacity: u32 = 1,
|
||||||
name: ?[]const u8 = null,
|
name: ?[]const u8 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn GenericBuffer(comptime Header: type, comptime Element: type) type {
|
pub fn GenericBuffer(comptime T: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
buffer: vk.Buffer,
|
buffer: vk.Buffer,
|
||||||
device_memory: vk.DeviceMemory,
|
device_memory: vk.DeviceMemory,
|
||||||
|
mappable: bool,
|
||||||
target_queue: TargetQueue,
|
target_queue: TargetQueue,
|
||||||
array_capacity: u32,
|
capacity: u32,
|
||||||
|
|
||||||
const header_size: u32 = @sizeOf(Header);
|
const element_size: u32 = @sizeOf(T);
|
||||||
const element_size: u32 = @sizeOf(Element);
|
|
||||||
const array_offset: u32 = std.mem.alignForward(usize, header_size, @alignOf(Element));
|
|
||||||
|
|
||||||
pub const WriteInfo = struct {
|
|
||||||
header: ?Header = null,
|
|
||||||
element_offset: u32 = 0,
|
|
||||||
elements: []const Element = &.{},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn init(init_info: InitInfo) !Self {
|
pub fn init(init_info: InitInfo) !Self {
|
||||||
const engine = ctx.engine;
|
const engine = ctx.engine;
|
||||||
|
|
||||||
const array_size = try std.math.mul(u32, init_info.array_capacity, element_size);
|
const size = try std.math.mul(u32, init_info.capacity, element_size);
|
||||||
const size = try std.math.add(u32, array_offset, array_size);
|
|
||||||
|
|
||||||
const target_queue_family = switch (init_info.target_queue) {
|
const target_queue_family = switch (init_info.target_queue) {
|
||||||
.graphics => engine.graphics_queue.allocation.family,
|
.graphics => engine.graphics_queue.allocation.family,
|
||||||
@@ -71,14 +65,17 @@ pub fn GenericBuffer(comptime Header: type, comptime Element: type) type {
|
|||||||
});
|
});
|
||||||
errdefer engine.destroyBuffer(buffer);
|
errdefer engine.destroyBuffer(buffer);
|
||||||
if (init_info.name) |name| {
|
if (init_info.name) |name| {
|
||||||
engine.setObjectName(buffer, "B {s} [{s}, {s}]", .{ name, @typeName(Header), @typeName(Element) });
|
engine.setObjectName(buffer, "B {s} [{s}]", .{ name, @typeName(T) });
|
||||||
}
|
}
|
||||||
|
|
||||||
const memory_requirements = engine.getBufferMemoryRequirements(buffer);
|
const memory_requirements = engine.getBufferMemoryRequirements(buffer);
|
||||||
const device_memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
const device_memory = try engine.allocate(memory_requirements, .{
|
||||||
|
.device_local_bit = !init_info.mappable,
|
||||||
|
.host_visible_bit = init_info.mappable,
|
||||||
|
});
|
||||||
errdefer engine.freeMemory(device_memory);
|
errdefer engine.freeMemory(device_memory);
|
||||||
if (init_info.name) |name| {
|
if (init_info.name) |name| {
|
||||||
engine.setObjectName(device_memory, "DM {s} [{s}, {s}]", .{ name, @typeName(Header), @typeName(Element) });
|
engine.setObjectName(device_memory, "DM {s} [{s}]", .{ name, @typeName(T) });
|
||||||
}
|
}
|
||||||
|
|
||||||
try engine.bindBufferMemory(buffer, device_memory, 0);
|
try engine.bindBufferMemory(buffer, device_memory, 0);
|
||||||
@@ -86,8 +83,9 @@ pub fn GenericBuffer(comptime Header: type, comptime Element: type) type {
|
|||||||
return .{
|
return .{
|
||||||
.buffer = buffer,
|
.buffer = buffer,
|
||||||
.device_memory = device_memory,
|
.device_memory = device_memory,
|
||||||
|
.mappable = init_info.mappable,
|
||||||
.target_queue = init_info.target_queue,
|
.target_queue = init_info.target_queue,
|
||||||
.array_capacity = init_info.array_capacity,
|
.capacity = init_info.capacity,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,120 +100,130 @@ pub fn GenericBuffer(comptime Header: type, comptime Element: type) type {
|
|||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(self: Self, write_info: WriteInfo) !void {
|
pub fn write(self: Self, value: T) !void {
|
||||||
const engine = ctx.engine;
|
return self.writeSliceAt(0, &.{value});
|
||||||
|
|
||||||
const element_count = std.math.cast(u32, write_info.elements.len) orelse return error.Overflow;
|
|
||||||
std.debug.assert(write_info.element_offset + element_count <= self.array_capacity);
|
|
||||||
|
|
||||||
const header_write_size: u32 = if (write_info.header != null) header_size else 0;
|
|
||||||
const array_write_size = element_count * element_size;
|
|
||||||
const array_write_offset: u32 = if (header_write_size > 0) array_offset else 0;
|
|
||||||
const write_size = array_write_offset + array_write_size;
|
|
||||||
|
|
||||||
if (write_size == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var regions_buffer: [2]vk.BufferCopy = undefined;
|
|
||||||
var regions: std.ArrayList(vk.BufferCopy) = .initBuffer(®ions_buffer);
|
|
||||||
|
|
||||||
// One continuous write
|
|
||||||
if (header_write_size > 0 and array_write_size > 0 and write_info.element_offset == 0) {
|
|
||||||
regions.appendAssumeCapacity(.{
|
|
||||||
.src_offset = 0,
|
|
||||||
.dst_offset = 0,
|
|
||||||
.size = write_size,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Separate writes for header and array (if they exist)
|
|
||||||
else {
|
|
||||||
if (header_write_size > 0) {
|
|
||||||
regions.appendAssumeCapacity(.{
|
|
||||||
.src_offset = 0,
|
|
||||||
.dst_offset = 0,
|
|
||||||
.size = header_write_size,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (array_write_size > 0) {
|
|
||||||
regions.appendAssumeCapacity(.{
|
|
||||||
.src_offset = array_write_offset,
|
|
||||||
.dst_offset = array_offset + write_info.element_offset * element_size,
|
|
||||||
.size = array_write_size,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std.debug.assert(regions.items.len > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
var staging_buffer: StagingBuffer = try .init(.{
|
|
||||||
.target_queue = self.target_queue,
|
|
||||||
.capacity = write_size,
|
|
||||||
});
|
|
||||||
defer staging_buffer.deinit();
|
|
||||||
|
|
||||||
const staging_memory = try staging_buffer.map();
|
|
||||||
if (write_info.header) |header| {
|
|
||||||
@memcpy(staging_memory[0..header_size], std.mem.asBytes(&header));
|
|
||||||
}
|
|
||||||
@memcpy(staging_memory[array_write_offset..write_size], std.mem.sliceAsBytes(write_info.elements));
|
|
||||||
staging_buffer.unmap();
|
|
||||||
|
|
||||||
var command_buffer: CommandBuffer = try .init(.transfer);
|
|
||||||
defer command_buffer.deinit();
|
|
||||||
|
|
||||||
try command_buffer.beginCommandBuffer();
|
|
||||||
command_buffer.copyBuffer(staging_buffer.buffer, self.buffer, regions.items);
|
|
||||||
try command_buffer.endCommandBuffer();
|
|
||||||
|
|
||||||
const fence = try engine.createFence(.{});
|
|
||||||
defer engine.destroyFence(fence);
|
|
||||||
|
|
||||||
try command_buffer.submit(.{ .fence = fence });
|
|
||||||
try engine.waitForFence(fence);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writeRaw(self: Self, data_offset: u32, data: []const u8) !void {
|
pub fn writeAt(self: Self, index: u32, value: T) !void {
|
||||||
|
return self.writeSliceAt(index, &.{value});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeSlice(self: Self, values: []const T) !void {
|
||||||
|
return self.writeSliceAt(0, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeSliceAt(self: Self, first_index: u32, values: []const T) !void {
|
||||||
const engine = ctx.engine;
|
const engine = ctx.engine;
|
||||||
|
|
||||||
const array_size = self.array_capacity * element_size;
|
const element_count = std.math.cast(u32, values.len) orelse return error.Overflow;
|
||||||
const size = array_offset + array_size;
|
std.debug.assert(first_index + element_count <= self.capacity);
|
||||||
|
|
||||||
const data_size = std.math.cast(u32, data.len) orelse return error.Overflow;
|
const write_size = element_count * element_size;
|
||||||
std.debug.assert(data_offset + data_size <= size);
|
if (element_count == 0) return;
|
||||||
|
|
||||||
var staging_buffer = try StagingBuffer.init(engine, .{
|
if (self.mappable) {
|
||||||
.target_queue = self.target_queue,
|
const slice = try self.mapSliceAt(first_index, element_count);
|
||||||
.capacity = data_size,
|
defer self.unmap();
|
||||||
});
|
|
||||||
defer staging_buffer.deinit(engine);
|
|
||||||
|
|
||||||
const staging_memory = try staging_buffer.map(engine);
|
@memcpy(slice, values);
|
||||||
@memcpy(staging_memory, data);
|
try self.flush(first_index, element_count);
|
||||||
staging_buffer.unmap(engine);
|
} else {
|
||||||
|
var staging_buffer: GenericBuffer(T) = try .init(.{
|
||||||
|
.usage = .staging,
|
||||||
|
.target_queue = self.target_queue,
|
||||||
|
.mappable = true,
|
||||||
|
.capacity = element_count,
|
||||||
|
});
|
||||||
|
defer staging_buffer.deinit();
|
||||||
|
|
||||||
var command_buffer = try CommandBuffer.init(engine, .transfer);
|
{
|
||||||
defer command_buffer.deinit(engine);
|
const staging_slice = try staging_buffer.mapSlice();
|
||||||
|
defer staging_buffer.unmap();
|
||||||
|
|
||||||
try command_buffer.beginCommandBuffer(.{});
|
@memcpy(staging_slice, values);
|
||||||
command_buffer.copyBuffer(
|
try staging_buffer.flush(0, element_count);
|
||||||
staging_buffer.buffer,
|
}
|
||||||
self.buffer,
|
|
||||||
&.{
|
var command_buffer: CommandBuffer = try .init(.transfer);
|
||||||
|
defer command_buffer.deinit();
|
||||||
|
|
||||||
|
try command_buffer.beginCommandBuffer();
|
||||||
|
const regions = [_]vk.BufferCopy{
|
||||||
.{
|
.{
|
||||||
.src_offset = 0,
|
.src_offset = 0,
|
||||||
.dst_offset = data_offset,
|
.dst_offset = first_index * element_size,
|
||||||
.size = data_size,
|
.size = write_size,
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
command_buffer.copyBuffer(staging_buffer.buffer, self.buffer, ®ions);
|
||||||
|
try command_buffer.endCommandBuffer();
|
||||||
|
|
||||||
|
const fence = try engine.createFence(.{});
|
||||||
|
defer engine.destroyFence(fence);
|
||||||
|
|
||||||
|
try command_buffer.submit(.{ .fence = fence });
|
||||||
|
try engine.waitForFence(fence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map(self: Self) !*T {
|
||||||
|
std.debug.assert(self.mappable);
|
||||||
|
|
||||||
|
const slice = try self.mapSliceAt(0, 1);
|
||||||
|
return &slice[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mapAt(self: Self, index: u32) !*T {
|
||||||
|
std.debug.assert(self.mappable);
|
||||||
|
|
||||||
|
const slice = try self.mapSliceAt(index, 1);
|
||||||
|
return &slice[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mapSlice(self: Self) ![]T {
|
||||||
|
std.debug.assert(self.mappable);
|
||||||
|
|
||||||
|
return self.mapSliceAt(0, self.capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mapSliceAt(self: Self, first_index: u32, count: u32) ![]T {
|
||||||
|
const engine = ctx.engine;
|
||||||
|
|
||||||
|
std.debug.assert(self.mappable);
|
||||||
|
std.debug.assert(first_index + count <= self.capacity);
|
||||||
|
|
||||||
|
const byte_offset = @as(u64, first_index) * @as(u64, element_size);
|
||||||
|
const byte_length = @as(u64, count) * @as(u64, element_size);
|
||||||
|
|
||||||
|
const mapped_memory = try engine.mapMemory(self.device_memory, byte_offset, byte_length, .{});
|
||||||
|
const slice = @as([*]T, @ptrCast(@alignCast(mapped_memory.ptr)))[0..count];
|
||||||
|
|
||||||
|
return slice;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flush(self: Self, first_index: u32, count: u32) !void {
|
||||||
|
const engine = ctx.engine;
|
||||||
|
|
||||||
|
std.debug.assert(self.mappable);
|
||||||
|
std.debug.assert(first_index + count <= self.capacity);
|
||||||
|
|
||||||
|
const byte_offset = @as(u64, first_index) * @as(u64, element_size);
|
||||||
|
const byte_length = @as(u64, count) * @as(u64, element_size);
|
||||||
|
|
||||||
|
try engine.device.flushMappedMemoryRanges(&.{
|
||||||
|
.{
|
||||||
|
.memory = self.device_memory,
|
||||||
|
.offset = byte_offset,
|
||||||
|
.size = byte_length,
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
try command_buffer.endCommandBuffer();
|
}
|
||||||
|
|
||||||
const fence = try engine.createFence(.{});
|
pub fn unmap(self: Self) void {
|
||||||
defer engine.destroyFence(fence);
|
const engine = ctx.engine;
|
||||||
|
|
||||||
try command_buffer.submit(engine, .{ .fence = fence });
|
std.debug.assert(self.mappable);
|
||||||
try engine.waitForFence(fence);
|
engine.unmapMemory(self.device_memory);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const vm = @import("vecmath");
|
|||||||
|
|
||||||
const CommandBuffer = @import("CommandBuffer.zig");
|
const CommandBuffer = @import("CommandBuffer.zig");
|
||||||
const Engine = @import("Engine.zig");
|
const Engine = @import("Engine.zig");
|
||||||
|
const Fonts = @import("Fonts.zig");
|
||||||
const GenericBuffer = @import("GenericBuffer.zig").GenericBuffer;
|
const GenericBuffer = @import("GenericBuffer.zig").GenericBuffer;
|
||||||
const Swapchain = @import("Swapchain.zig");
|
const Swapchain = @import("Swapchain.zig");
|
||||||
const Texture = @import("Texture.zig");
|
const Texture = @import("Texture.zig");
|
||||||
@@ -25,19 +26,18 @@ pub const Draw = struct {
|
|||||||
|
|
||||||
pub const Text = extern struct {
|
pub const Text = extern struct {
|
||||||
color: vm.Vector4,
|
color: vm.Vector4,
|
||||||
pos_min: vm.Vector2,
|
bottom_left_sspx: vm.Vector2,
|
||||||
pos_max: vm.Vector2,
|
font_size_px: f32,
|
||||||
uv_min: vm.Vector2,
|
glyph_id: u16,
|
||||||
uv_max: vm.Vector2,
|
font_id: Fonts.Id,
|
||||||
atlas_id: Textures.Id,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Image = extern struct {
|
pub const Image = extern struct {
|
||||||
tint: vm.Vector4,
|
tint: vm.Vector4 = .initScalar(1),
|
||||||
position_sspx: vm.Vector2,
|
position_sspx: vm.Vector2,
|
||||||
size_px: vm.Vector2,
|
size_px: vm.Vector2,
|
||||||
uv_min: vm.Vector2,
|
uv_min: vm.Vector2 = .initScalar(0),
|
||||||
uv_max: vm.Vector2,
|
uv_max: vm.Vector2 = .initScalar(1),
|
||||||
texture_id: Textures.Id,
|
texture_id: Textures.Id,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -48,9 +48,9 @@ pub const Batch = struct {
|
|||||||
instance_count: u32,
|
instance_count: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
box_gpu_buffer: GenericBuffer(void, Draw.Box),
|
box_gpu_buffer: GenericBuffer(Draw.Box),
|
||||||
text_gpu_buffer: GenericBuffer(void, Draw.Text),
|
text_gpu_buffer: GenericBuffer(Draw.Text),
|
||||||
image_gpu_buffer: GenericBuffer(void, Draw.Image),
|
image_gpu_buffer: GenericBuffer(Draw.Image),
|
||||||
|
|
||||||
box_cpu_buffer: std.ArrayList(Draw.Box),
|
box_cpu_buffer: std.ArrayList(Draw.Box),
|
||||||
text_cpu_buffer: std.ArrayList(Draw.Text),
|
text_cpu_buffer: std.ArrayList(Draw.Text),
|
||||||
@@ -65,8 +65,18 @@ box_descriptor_set_layout: vk.DescriptorSetLayout,
|
|||||||
box_pipeline_layout: vk.PipelineLayout,
|
box_pipeline_layout: vk.PipelineLayout,
|
||||||
box_pipeline: vk.Pipeline,
|
box_pipeline: vk.Pipeline,
|
||||||
|
|
||||||
|
text_descriptor_set_layout: vk.DescriptorSetLayout,
|
||||||
|
text_pipeline_layout: vk.PipelineLayout,
|
||||||
|
text_pipeline: vk.Pipeline,
|
||||||
|
|
||||||
|
image_descriptor_set_layout: vk.DescriptorSetLayout,
|
||||||
|
image_pipeline_layout: vk.PipelineLayout,
|
||||||
|
image_pipeline: vk.Pipeline,
|
||||||
|
|
||||||
descriptor_pool: vk.DescriptorPool,
|
descriptor_pool: vk.DescriptorPool,
|
||||||
box_descriptor_set: vk.DescriptorSet,
|
box_descriptor_set: vk.DescriptorSet,
|
||||||
|
text_descriptor_set: vk.DescriptorSet,
|
||||||
|
image_descriptor_set: vk.DescriptorSet,
|
||||||
|
|
||||||
pub const max_box_draws = 1024;
|
pub const max_box_draws = 1024;
|
||||||
pub const max_image_draws = 1024;
|
pub const max_image_draws = 1024;
|
||||||
@@ -81,26 +91,26 @@ pub fn init() !*Gui {
|
|||||||
|
|
||||||
const gui = try allocator_persistent.create(Gui);
|
const gui = try allocator_persistent.create(Gui);
|
||||||
|
|
||||||
var box_gpu_buffer: GenericBuffer(void, Draw.Box) = try .init(.{
|
var box_gpu_buffer: GenericBuffer(Draw.Box) = try .init(.{
|
||||||
.usage = .storage,
|
.usage = .storage,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
.array_capacity = max_box_draws,
|
.capacity = max_box_draws,
|
||||||
.name = "GUI box draws",
|
.name = "GUI box draws",
|
||||||
});
|
});
|
||||||
errdefer box_gpu_buffer.deinit();
|
errdefer box_gpu_buffer.deinit();
|
||||||
|
|
||||||
var text_gpu_buffer: GenericBuffer(void, Draw.Text) = try .init(.{
|
var text_gpu_buffer: GenericBuffer(Draw.Text) = try .init(.{
|
||||||
.usage = .storage,
|
.usage = .storage,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
.array_capacity = max_text_draws,
|
.capacity = max_text_draws,
|
||||||
.name = "GUI text draws",
|
.name = "GUI text draws",
|
||||||
});
|
});
|
||||||
errdefer text_gpu_buffer.deinit();
|
errdefer text_gpu_buffer.deinit();
|
||||||
|
|
||||||
var image_gpu_buffer: GenericBuffer(void, Draw.Image) = try .init(.{
|
var image_gpu_buffer: GenericBuffer(Draw.Image) = try .init(.{
|
||||||
.usage = .storage,
|
.usage = .storage,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
.array_capacity = max_image_draws,
|
.capacity = max_image_draws,
|
||||||
.name = "GUI image draws",
|
.name = "GUI image draws",
|
||||||
});
|
});
|
||||||
errdefer image_gpu_buffer.deinit();
|
errdefer image_gpu_buffer.deinit();
|
||||||
@@ -108,15 +118,24 @@ pub fn init() !*Gui {
|
|||||||
const box_cpu_buffer: std.ArrayList(Draw.Box) = try .initCapacity(allocator_persistent, max_box_draws);
|
const box_cpu_buffer: std.ArrayList(Draw.Box) = try .initCapacity(allocator_persistent, max_box_draws);
|
||||||
const text_cpu_buffer: std.ArrayList(Draw.Text) = try .initCapacity(allocator_persistent, max_text_draws);
|
const text_cpu_buffer: std.ArrayList(Draw.Text) = try .initCapacity(allocator_persistent, max_text_draws);
|
||||||
const image_cpu_buffer: std.ArrayList(Draw.Image) = try .initCapacity(allocator_persistent, max_image_draws);
|
const image_cpu_buffer: std.ArrayList(Draw.Image) = try .initCapacity(allocator_persistent, max_image_draws);
|
||||||
|
|
||||||
const batches: std.ArrayList(Batch) = try .initCapacity(allocator_persistent, max_batches);
|
const batches: std.ArrayList(Batch) = try .initCapacity(allocator_persistent, max_batches);
|
||||||
|
var index_buffer = try shaders.IndexBuffer.init(.{
|
||||||
|
.usage = .index,
|
||||||
|
.target_queue = .graphics,
|
||||||
|
.capacity = 6,
|
||||||
|
.name = "QuadIB",
|
||||||
|
});
|
||||||
|
errdefer index_buffer.deinit();
|
||||||
|
try index_buffer.writeSlice(&.{ 0, 1, 2, 2, 1, 3 });
|
||||||
|
|
||||||
const sampler = try engine.createSampler(.{
|
const sampler = try engine.createSampler(.{
|
||||||
.mag_filter = .linear,
|
.mag_filter = .linear,
|
||||||
.min_filter = .linear,
|
.min_filter = .linear,
|
||||||
.mipmap_mode = .linear,
|
.mipmap_mode = .linear,
|
||||||
.address_mode_u = .repeat,
|
.address_mode_u = .clamp_to_edge,
|
||||||
.address_mode_v = .repeat,
|
.address_mode_v = .clamp_to_edge,
|
||||||
.address_mode_w = .repeat,
|
.address_mode_w = .clamp_to_edge,
|
||||||
.mip_lod_bias = 0,
|
.mip_lod_bias = 0,
|
||||||
.anisotropy_enable = .false,
|
.anisotropy_enable = .false,
|
||||||
.max_anisotropy = 0,
|
.max_anisotropy = 0,
|
||||||
@@ -156,36 +175,21 @@ pub fn init() !*Gui {
|
|||||||
errdefer engine.destroyPipelineLayout(box_pipeline_layout);
|
errdefer engine.destroyPipelineLayout(box_pipeline_layout);
|
||||||
engine.setObjectName(box_pipeline_layout, "PL GUI Box", .{});
|
engine.setObjectName(box_pipeline_layout, "PL GUI Box", .{});
|
||||||
|
|
||||||
const box_vertex_shader = try engine.createShaderModule(.{ .code = &shaders.gui_box_vert_spv });
|
const box_shader = try engine.createShaderModule(.{ .code = &shaders.gui_box_spv });
|
||||||
defer engine.destroyShaderModule(box_vertex_shader);
|
defer engine.destroyShaderModule(box_shader);
|
||||||
engine.setObjectName(box_vertex_shader, "SM gui_box_vert", .{});
|
engine.setObjectName(box_shader, "SM gui_box", .{});
|
||||||
|
|
||||||
const box_fragment_shader = try engine.createShaderModule(.{ .code = &shaders.gui_box_frag_spv });
|
|
||||||
defer engine.destroyShaderModule(box_fragment_shader);
|
|
||||||
engine.setObjectName(box_fragment_shader, "SM gui_box_frag", .{});
|
|
||||||
|
|
||||||
var index_buffer = try shaders.IndexBuffer.init(.{
|
|
||||||
.usage = .index,
|
|
||||||
.target_queue = .graphics,
|
|
||||||
.array_capacity = 6,
|
|
||||||
.name = "QuadIB",
|
|
||||||
});
|
|
||||||
errdefer index_buffer.deinit();
|
|
||||||
try index_buffer.write(.{
|
|
||||||
.elements = &.{ 0, 1, 2, 2, 1, 3 },
|
|
||||||
});
|
|
||||||
|
|
||||||
const box_pipeline = try engine.createGraphicsPipeline(.{
|
const box_pipeline = try engine.createGraphicsPipeline(.{
|
||||||
.stages = &.{
|
.stages = &.{
|
||||||
.{
|
.{
|
||||||
.stage = .{ .vertex_bit = true },
|
.stage = .{ .vertex_bit = true },
|
||||||
.module = box_vertex_shader,
|
.module = box_shader,
|
||||||
.name = "main",
|
.name = "vertexMain",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.stage = .{ .fragment_bit = true },
|
.stage = .{ .fragment_bit = true },
|
||||||
.module = box_fragment_shader,
|
.module = box_shader,
|
||||||
.name = "main",
|
.name = "fragmentMain",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.vertex_input_state = .{},
|
.vertex_input_state = .{},
|
||||||
@@ -277,16 +281,325 @@ pub fn init() !*Gui {
|
|||||||
errdefer engine.destroyPipeline(box_pipeline);
|
errdefer engine.destroyPipeline(box_pipeline);
|
||||||
engine.setObjectName(box_pipeline, "P GUI Box", .{});
|
engine.setObjectName(box_pipeline, "P GUI Box", .{});
|
||||||
|
|
||||||
|
const text_descriptor_set_layout = try engine.createDescriptorSetLayout(.{
|
||||||
|
.bindings = &.{
|
||||||
|
.{
|
||||||
|
.binding = 0,
|
||||||
|
.descriptor_type = .uniform_buffer,
|
||||||
|
.descriptor_count = 1,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 1,
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_count = 1,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 2,
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_count = Fonts.max_fonts,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 3,
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_count = Fonts.max_fonts,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 4,
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_count = Fonts.max_fonts,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
errdefer engine.destroyDescriptorSetLayout(text_descriptor_set_layout);
|
||||||
|
engine.setObjectName(text_descriptor_set_layout, "DSL GUI Text", .{});
|
||||||
|
|
||||||
|
const text_pipeline_layout = try engine.createPipelineLayout(.{
|
||||||
|
.set_layouts = &.{
|
||||||
|
text_descriptor_set_layout,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
errdefer engine.destroyPipelineLayout(text_pipeline_layout);
|
||||||
|
engine.setObjectName(text_pipeline_layout, "PL GUI Text", .{});
|
||||||
|
|
||||||
|
const text_shader = try engine.createShaderModule(.{ .code = &shaders.gui_text_spv });
|
||||||
|
defer engine.destroyShaderModule(text_shader);
|
||||||
|
engine.setObjectName(text_shader, "SM gui_text", .{});
|
||||||
|
|
||||||
|
const text_pipeline = try engine.createGraphicsPipeline(.{
|
||||||
|
.stages = &.{
|
||||||
|
.{
|
||||||
|
.stage = .{ .vertex_bit = true },
|
||||||
|
.module = text_shader,
|
||||||
|
.name = "vertexMain",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.stage = .{ .fragment_bit = true },
|
||||||
|
.module = text_shader,
|
||||||
|
.name = "fragmentMain",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.vertex_input_state = .{},
|
||||||
|
.input_assembly_state = .{
|
||||||
|
.topology = .triangle_list,
|
||||||
|
.primitive_restart_enable = .false,
|
||||||
|
},
|
||||||
|
.viewport_state = .{
|
||||||
|
.viewports = &.{undefined}, // dynamic
|
||||||
|
.scissors = &.{undefined}, // dynamic
|
||||||
|
},
|
||||||
|
.rasterization_state = .{
|
||||||
|
.depth_clamp_enable = .false,
|
||||||
|
.rasterizer_discard_enable = .false,
|
||||||
|
.polygon_mode = .fill,
|
||||||
|
.cull_mode = .{},
|
||||||
|
.front_face = .counter_clockwise,
|
||||||
|
.depth_bias_enable = .false,
|
||||||
|
.depth_bias_constant_factor = 0,
|
||||||
|
.depth_bias_clamp = 0,
|
||||||
|
.depth_bias_slope_factor = 0,
|
||||||
|
.line_width = 1,
|
||||||
|
},
|
||||||
|
.multisample_state = .{
|
||||||
|
.rasterization_samples = .{ .@"1_bit" = true },
|
||||||
|
.sample_shading_enable = .false,
|
||||||
|
.min_sample_shading = 1,
|
||||||
|
.alpha_to_coverage_enable = .false,
|
||||||
|
.alpha_to_one_enable = .false,
|
||||||
|
},
|
||||||
|
.depth_stencil_state = .{
|
||||||
|
.depth_test_enable = .false,
|
||||||
|
.depth_write_enable = .false,
|
||||||
|
.depth_compare_op = .always,
|
||||||
|
.depth_bounds_test_enable = .false,
|
||||||
|
.stencil_test_enable = .false,
|
||||||
|
.front = .{
|
||||||
|
.fail_op = .keep,
|
||||||
|
.pass_op = .keep,
|
||||||
|
.depth_fail_op = .keep,
|
||||||
|
.compare_op = .never,
|
||||||
|
.compare_mask = 0,
|
||||||
|
.write_mask = 0,
|
||||||
|
.reference = 0,
|
||||||
|
},
|
||||||
|
.back = .{
|
||||||
|
.fail_op = .keep,
|
||||||
|
.pass_op = .keep,
|
||||||
|
.depth_fail_op = .keep,
|
||||||
|
.compare_op = .never,
|
||||||
|
.compare_mask = 0,
|
||||||
|
.write_mask = 0,
|
||||||
|
.reference = 0,
|
||||||
|
},
|
||||||
|
.min_depth_bounds = 0,
|
||||||
|
.max_depth_bounds = 1,
|
||||||
|
},
|
||||||
|
.color_blend_state = .{
|
||||||
|
.logic_op_enable = .false,
|
||||||
|
.logic_op = .copy,
|
||||||
|
.attachments = &.{
|
||||||
|
.{
|
||||||
|
.blend_enable = .true,
|
||||||
|
.src_color_blend_factor = .one,
|
||||||
|
.dst_color_blend_factor = .one_minus_src_alpha,
|
||||||
|
.color_blend_op = .add,
|
||||||
|
.src_alpha_blend_factor = .one,
|
||||||
|
.dst_alpha_blend_factor = .one_minus_src_alpha,
|
||||||
|
.alpha_blend_op = .add,
|
||||||
|
.color_write_mask = .{
|
||||||
|
.r_bit = true,
|
||||||
|
.g_bit = true,
|
||||||
|
.b_bit = true,
|
||||||
|
.a_bit = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.blend_constants = .{ 0, 0, 0, 0 },
|
||||||
|
},
|
||||||
|
.dynamic_state = .{
|
||||||
|
.dynamic_states = &.{ .viewport, .scissor },
|
||||||
|
},
|
||||||
|
.layout = text_pipeline_layout,
|
||||||
|
.view_mask = 0,
|
||||||
|
.color_attachment_formats = &.{swapchain.params.surface_format.format},
|
||||||
|
.depth_attachment_format = Texture.Usage.depth.vkFormat(),
|
||||||
|
.stencil_attachment_format = .undefined,
|
||||||
|
});
|
||||||
|
errdefer engine.destroyPipeline(text_pipeline);
|
||||||
|
engine.setObjectName(text_pipeline, "P GUI Box", .{});
|
||||||
|
|
||||||
|
const image_descriptor_set_layout = try engine.createDescriptorSetLayout(.{
|
||||||
|
.bindings = &.{
|
||||||
|
.{
|
||||||
|
.binding = 0,
|
||||||
|
.descriptor_type = .uniform_buffer,
|
||||||
|
.descriptor_count = 1,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 1,
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_count = 1,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 2,
|
||||||
|
.descriptor_type = .sampler,
|
||||||
|
.descriptor_count = 1,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
.immutable_samplers = &.{sampler},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 3,
|
||||||
|
.descriptor_type = .sampled_image,
|
||||||
|
.descriptor_count = Textures.max_textures,
|
||||||
|
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
errdefer engine.destroyDescriptorSetLayout(image_descriptor_set_layout);
|
||||||
|
engine.setObjectName(box_descriptor_set_layout, "DSL GUI Image", .{});
|
||||||
|
|
||||||
|
const image_pipeline_layout = try engine.createPipelineLayout(.{
|
||||||
|
.set_layouts = &.{
|
||||||
|
image_descriptor_set_layout,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
errdefer engine.destroyPipelineLayout(image_pipeline_layout);
|
||||||
|
engine.setObjectName(box_pipeline_layout, "PL GUI Image", .{});
|
||||||
|
|
||||||
|
const image_shader = try engine.createShaderModule(.{ .code = &shaders.gui_image_spv });
|
||||||
|
defer engine.destroyShaderModule(image_shader);
|
||||||
|
engine.setObjectName(text_shader, "SM gui_image", .{});
|
||||||
|
|
||||||
|
const image_pipeline = try engine.createGraphicsPipeline(.{
|
||||||
|
.stages = &.{
|
||||||
|
.{
|
||||||
|
.stage = .{ .vertex_bit = true },
|
||||||
|
.module = image_shader,
|
||||||
|
.name = "vertexMain",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.stage = .{ .fragment_bit = true },
|
||||||
|
.module = image_shader,
|
||||||
|
.name = "fragmentMain",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.vertex_input_state = .{},
|
||||||
|
.input_assembly_state = .{
|
||||||
|
.topology = .triangle_list,
|
||||||
|
.primitive_restart_enable = .false,
|
||||||
|
},
|
||||||
|
.viewport_state = .{
|
||||||
|
.viewports = &.{undefined}, // dynamic
|
||||||
|
.scissors = &.{undefined}, // dynamic
|
||||||
|
},
|
||||||
|
.rasterization_state = .{
|
||||||
|
.depth_clamp_enable = .false,
|
||||||
|
.rasterizer_discard_enable = .false,
|
||||||
|
.polygon_mode = .fill,
|
||||||
|
.cull_mode = .{},
|
||||||
|
.front_face = .counter_clockwise,
|
||||||
|
.depth_bias_enable = .false,
|
||||||
|
.depth_bias_constant_factor = 0,
|
||||||
|
.depth_bias_clamp = 0,
|
||||||
|
.depth_bias_slope_factor = 0,
|
||||||
|
.line_width = 1,
|
||||||
|
},
|
||||||
|
.multisample_state = .{
|
||||||
|
.rasterization_samples = .{ .@"1_bit" = true },
|
||||||
|
.sample_shading_enable = .false,
|
||||||
|
.min_sample_shading = 1,
|
||||||
|
.alpha_to_coverage_enable = .false,
|
||||||
|
.alpha_to_one_enable = .false,
|
||||||
|
},
|
||||||
|
.depth_stencil_state = .{
|
||||||
|
.depth_test_enable = .false,
|
||||||
|
.depth_write_enable = .false,
|
||||||
|
.depth_compare_op = .always,
|
||||||
|
.depth_bounds_test_enable = .false,
|
||||||
|
.stencil_test_enable = .false,
|
||||||
|
.front = .{
|
||||||
|
.fail_op = .keep,
|
||||||
|
.pass_op = .keep,
|
||||||
|
.depth_fail_op = .keep,
|
||||||
|
.compare_op = .never,
|
||||||
|
.compare_mask = 0,
|
||||||
|
.write_mask = 0,
|
||||||
|
.reference = 0,
|
||||||
|
},
|
||||||
|
.back = .{
|
||||||
|
.fail_op = .keep,
|
||||||
|
.pass_op = .keep,
|
||||||
|
.depth_fail_op = .keep,
|
||||||
|
.compare_op = .never,
|
||||||
|
.compare_mask = 0,
|
||||||
|
.write_mask = 0,
|
||||||
|
.reference = 0,
|
||||||
|
},
|
||||||
|
.min_depth_bounds = 0,
|
||||||
|
.max_depth_bounds = 1,
|
||||||
|
},
|
||||||
|
.color_blend_state = .{
|
||||||
|
.logic_op_enable = .false,
|
||||||
|
.logic_op = .copy,
|
||||||
|
.attachments = &.{
|
||||||
|
.{
|
||||||
|
.blend_enable = .true,
|
||||||
|
.src_color_blend_factor = .one,
|
||||||
|
.dst_color_blend_factor = .one_minus_src_alpha,
|
||||||
|
.color_blend_op = .add,
|
||||||
|
.src_alpha_blend_factor = .one,
|
||||||
|
.dst_alpha_blend_factor = .one_minus_src_alpha,
|
||||||
|
.alpha_blend_op = .add,
|
||||||
|
.color_write_mask = .{
|
||||||
|
.r_bit = true,
|
||||||
|
.g_bit = true,
|
||||||
|
.b_bit = true,
|
||||||
|
.a_bit = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.blend_constants = .{ 0, 0, 0, 0 },
|
||||||
|
},
|
||||||
|
.dynamic_state = .{
|
||||||
|
.dynamic_states = &.{ .viewport, .scissor },
|
||||||
|
},
|
||||||
|
.layout = image_pipeline_layout,
|
||||||
|
.view_mask = 0,
|
||||||
|
.color_attachment_formats = &.{swapchain.params.surface_format.format},
|
||||||
|
.depth_attachment_format = Texture.Usage.depth.vkFormat(),
|
||||||
|
.stencil_attachment_format = .undefined,
|
||||||
|
});
|
||||||
|
errdefer engine.destroyPipeline(image_pipeline);
|
||||||
|
engine.setObjectName(image_pipeline, "P GUI Image", .{});
|
||||||
|
|
||||||
const descriptor_pool = try engine.createDescriptorPool(.{
|
const descriptor_pool = try engine.createDescriptorPool(.{
|
||||||
.max_sets = 1,
|
.max_sets = 3,
|
||||||
.pool_sizes = &.{
|
.pool_sizes = &.{
|
||||||
.{
|
.{
|
||||||
.type = .uniform_buffer,
|
.type = .uniform_buffer,
|
||||||
.descriptor_count = 1,
|
.descriptor_count = 1 + // box
|
||||||
|
1 + // text
|
||||||
|
1, // image
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.type = .storage_buffer,
|
.type = .storage_buffer,
|
||||||
.descriptor_count = 1,
|
.descriptor_count = 1 + // box
|
||||||
|
(1 + 3 * Fonts.max_fonts) + // text
|
||||||
|
1, // image
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.type = .sampled_image,
|
||||||
|
.descriptor_count = Textures.max_textures, // image
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.type = .sampler,
|
||||||
|
.descriptor_count = 1, // image
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -299,6 +612,18 @@ pub fn init() !*Gui {
|
|||||||
});
|
});
|
||||||
engine.setObjectName(box_descriptor_set, "DS GUI Box", .{});
|
engine.setObjectName(box_descriptor_set, "DS GUI Box", .{});
|
||||||
|
|
||||||
|
const text_descriptor_set = try engine.allocateDescriptorSet(.{
|
||||||
|
.descriptor_pool = descriptor_pool,
|
||||||
|
.set_layout = text_descriptor_set_layout,
|
||||||
|
});
|
||||||
|
engine.setObjectName(text_descriptor_set, "DS GUI Text", .{});
|
||||||
|
|
||||||
|
const image_descriptor_set = try engine.allocateDescriptorSet(.{
|
||||||
|
.descriptor_pool = descriptor_pool,
|
||||||
|
.set_layout = image_descriptor_set_layout,
|
||||||
|
});
|
||||||
|
engine.setObjectName(image_descriptor_set, "DS GUI Image", .{});
|
||||||
|
|
||||||
try engine.updateDescriptorSets(.{
|
try engine.updateDescriptorSets(.{
|
||||||
.writes = &.{
|
.writes = &.{
|
||||||
.{
|
.{
|
||||||
@@ -316,6 +641,36 @@ pub fn init() !*Gui {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
.{
|
||||||
|
.dst_set = text_descriptor_set,
|
||||||
|
.dst_binding = 1,
|
||||||
|
.dst_array_element = 0,
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_infos = .{
|
||||||
|
.buffer = &.{
|
||||||
|
.{
|
||||||
|
.buffer = text_gpu_buffer.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.range = vk.WHOLE_SIZE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.dst_set = image_descriptor_set,
|
||||||
|
.dst_binding = 1,
|
||||||
|
.dst_array_element = 0,
|
||||||
|
.descriptor_type = .storage_buffer,
|
||||||
|
.descriptor_infos = .{
|
||||||
|
.buffer = &.{
|
||||||
|
.{
|
||||||
|
.buffer = image_gpu_buffer.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.range = vk.WHOLE_SIZE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -337,8 +692,18 @@ pub fn init() !*Gui {
|
|||||||
.box_pipeline_layout = box_pipeline_layout,
|
.box_pipeline_layout = box_pipeline_layout,
|
||||||
.box_pipeline = box_pipeline,
|
.box_pipeline = box_pipeline,
|
||||||
|
|
||||||
|
.text_descriptor_set_layout = text_descriptor_set_layout,
|
||||||
|
.text_pipeline_layout = text_pipeline_layout,
|
||||||
|
.text_pipeline = text_pipeline,
|
||||||
|
|
||||||
|
.image_descriptor_set_layout = image_descriptor_set_layout,
|
||||||
|
.image_pipeline_layout = image_pipeline_layout,
|
||||||
|
.image_pipeline = image_pipeline,
|
||||||
|
|
||||||
.descriptor_pool = descriptor_pool,
|
.descriptor_pool = descriptor_pool,
|
||||||
.box_descriptor_set = box_descriptor_set,
|
.box_descriptor_set = box_descriptor_set,
|
||||||
|
.text_descriptor_set = text_descriptor_set,
|
||||||
|
.image_descriptor_set = image_descriptor_set,
|
||||||
};
|
};
|
||||||
|
|
||||||
return gui;
|
return gui;
|
||||||
@@ -350,9 +715,15 @@ pub fn deinit(self: *Gui) void {
|
|||||||
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
|
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
|
||||||
|
|
||||||
engine.destroyDescriptorPool(self.descriptor_pool);
|
engine.destroyDescriptorPool(self.descriptor_pool);
|
||||||
|
engine.destroyPipeline(self.image_pipeline);
|
||||||
|
engine.destroyPipeline(self.text_pipeline);
|
||||||
engine.destroyPipeline(self.box_pipeline);
|
engine.destroyPipeline(self.box_pipeline);
|
||||||
self.index_buffer.deinit();
|
self.index_buffer.deinit();
|
||||||
|
engine.destroyPipelineLayout(self.image_pipeline_layout);
|
||||||
|
engine.destroyPipelineLayout(self.text_pipeline_layout);
|
||||||
engine.destroyPipelineLayout(self.box_pipeline_layout);
|
engine.destroyPipelineLayout(self.box_pipeline_layout);
|
||||||
|
engine.destroyDescriptorSetLayout(self.image_descriptor_set_layout);
|
||||||
|
engine.destroyDescriptorSetLayout(self.text_descriptor_set_layout);
|
||||||
engine.destroyDescriptorSetLayout(self.box_descriptor_set_layout);
|
engine.destroyDescriptorSetLayout(self.box_descriptor_set_layout);
|
||||||
engine.destroySampler(self.sampler);
|
engine.destroySampler(self.sampler);
|
||||||
|
|
||||||
@@ -383,6 +754,69 @@ pub fn pushText(self: *Gui, text: Draw.Text) void {
|
|||||||
self.extendOrAddBatch(.Text, instance);
|
self.extendOrAddBatch(.Text, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const PrintLineArgs = struct {
|
||||||
|
position_sspx: vm.Vector2,
|
||||||
|
font_id: Fonts.Id,
|
||||||
|
cap_height_px: f32,
|
||||||
|
color: vm.Vector4,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn printLine(self: *Gui, comptime fmt: []const u8, fmt_args: anytype, print_args: PrintLineArgs) void {
|
||||||
|
const allocator_frame = ctx.allocator_frame;
|
||||||
|
const fonts = ctx.fonts;
|
||||||
|
|
||||||
|
const text = std.fmt.allocPrint(allocator_frame, fmt, fmt_args) catch |err| {
|
||||||
|
std.log.err("Failed formatting of string \"{s}\": {}", .{ fmt, err });
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const font = fonts.array.items[@intFromEnum(print_args.font_id)];
|
||||||
|
const font_size_px = print_args.cap_height_px / font.cap_height_em;
|
||||||
|
|
||||||
|
const utf8_view = std.unicode.Utf8View.init(text) catch {
|
||||||
|
std.log.err("Invalid UTF-8 sequence in GUI print line", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
var cursor_sspx = print_args.position_sspx;
|
||||||
|
var maybe_prev_glyph: ?u16 = null;
|
||||||
|
var it = utf8_view.iterator();
|
||||||
|
while (it.nextCodepoint()) |codepoint| {
|
||||||
|
const glyph_id = font.codepoint_glyph_id.get(codepoint) orelse {
|
||||||
|
std.log.warn("Missing codepoint 0x{X} '{u}' in font #{d}", .{ codepoint, codepoint, print_args.font_id });
|
||||||
|
maybe_prev_glyph = null;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
defer maybe_prev_glyph = glyph_id;
|
||||||
|
|
||||||
|
const glyph_data = &font.cpu_glyph_data[glyph_id];
|
||||||
|
|
||||||
|
var cursor_offset_em = glyph_data.bottom_left_em;
|
||||||
|
cursor_offset_em.y = -cursor_offset_em.y;
|
||||||
|
if (maybe_prev_glyph == null) {
|
||||||
|
cursor_offset_em.x += glyph_data.left_side_bearing_em;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maybe_prev_glyph) |prev_glyph| {
|
||||||
|
const kerning_em = font.kerning_em.get(.{ prev_glyph, glyph_id }) orelse 0;
|
||||||
|
cursor_sspx.x += kerning_em;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glyph_data.gpu_glyph_id) |gpu_glyph_id| {
|
||||||
|
self.pushText(.{
|
||||||
|
.color = print_args.color,
|
||||||
|
.bottom_left_sspx = cursor_sspx.add(cursor_offset_em.mulScalar(font_size_px)),
|
||||||
|
.font_size_px = font_size_px,
|
||||||
|
.glyph_id = gpu_glyph_id,
|
||||||
|
.font_id = print_args.font_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const advance_px = font_size_px * glyph_data.advance_em;
|
||||||
|
cursor_sspx.x += advance_px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pushImage(self: *Gui, image: Draw.Image) void {
|
pub fn pushImage(self: *Gui, image: Draw.Image) void {
|
||||||
const instance: u32 = @intCast(self.image_cpu_buffer.items.len);
|
const instance: u32 = @intCast(self.image_cpu_buffer.items.len);
|
||||||
self.image_cpu_buffer.appendBounded(image) catch return;
|
self.image_cpu_buffer.appendBounded(image) catch return;
|
||||||
@@ -406,9 +840,9 @@ fn extendOrAddBatch(self: *Gui, draw_type: std.meta.DeclEnum(Draw), instance: u3
|
|||||||
pub fn draw(self: *const Gui, command_buffer: CommandBuffer) !void {
|
pub fn draw(self: *const Gui, command_buffer: CommandBuffer) !void {
|
||||||
command_buffer.bindIndexBuffer(self.index_buffer.buffer, 0, .uint16);
|
command_buffer.bindIndexBuffer(self.index_buffer.buffer, 0, .uint16);
|
||||||
|
|
||||||
try self.box_gpu_buffer.write(.{ .elements = self.box_cpu_buffer.items });
|
try self.box_gpu_buffer.writeSlice(self.box_cpu_buffer.items);
|
||||||
try self.text_gpu_buffer.write(.{ .elements = self.text_cpu_buffer.items });
|
try self.text_gpu_buffer.writeSlice(self.text_cpu_buffer.items);
|
||||||
try self.image_gpu_buffer.write(.{ .elements = self.image_cpu_buffer.items });
|
try self.image_gpu_buffer.writeSlice(self.image_cpu_buffer.items);
|
||||||
|
|
||||||
for (self.batches.items) |batch| {
|
for (self.batches.items) |batch| {
|
||||||
switch (batch.draw_type) {
|
switch (batch.draw_type) {
|
||||||
@@ -417,12 +851,12 @@ pub fn draw(self: *const Gui, command_buffer: CommandBuffer) !void {
|
|||||||
command_buffer.bindDescriptorSet(.graphics, self.box_pipeline_layout, 0, self.box_descriptor_set, null);
|
command_buffer.bindDescriptorSet(.graphics, self.box_pipeline_layout, 0, self.box_descriptor_set, null);
|
||||||
},
|
},
|
||||||
.Text => {
|
.Text => {
|
||||||
std.log.warn("GUI Text not implemented; skipping batch", .{});
|
command_buffer.bindPipeline(.graphics, self.text_pipeline);
|
||||||
continue;
|
command_buffer.bindDescriptorSet(.graphics, self.text_pipeline_layout, 0, self.text_descriptor_set, null);
|
||||||
},
|
},
|
||||||
.Image => {
|
.Image => {
|
||||||
std.log.warn("GUI Image not implemented; skipping batch", .{});
|
command_buffer.bindPipeline(.graphics, self.image_pipeline);
|
||||||
continue;
|
command_buffer.bindDescriptorSet(.graphics, self.image_pipeline_layout, 0, self.image_descriptor_set, null);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,11 +60,14 @@ pub const Key = struct {
|
|||||||
|
|
||||||
/// Maps a key value to a material ID. Preallocated with `allocator_persistent`.
|
/// Maps a key value to a material ID. Preallocated with `allocator_persistent`.
|
||||||
map: std.AutoHashMapUnmanaged(Key, Id),
|
map: std.AutoHashMapUnmanaged(Key, Id),
|
||||||
|
/// Stores all material data and maps a material ID to its data. Preallocated
|
||||||
|
/// with `allocator_persistent`.
|
||||||
|
///
|
||||||
|
/// Stores the same data as `material_buffer`, but is available on the CPU.
|
||||||
|
array: std.ArrayList(shaders.Material),
|
||||||
/// Stores all material data in a single contiguous storage buffer. Use the
|
/// Stores all material data in a single contiguous storage buffer. Use the
|
||||||
/// material ID as an index into this buffer.
|
/// material ID as an index into this buffer.
|
||||||
material_buffer: shaders.MaterialBuffer,
|
material_buffer: shaders.MaterialBuffer,
|
||||||
/// The amount of materials currently loaded.
|
|
||||||
material_count: usize,
|
|
||||||
|
|
||||||
/// With `@sizeOf(Material) == 52` and `max_materials == 4096`, the material
|
/// With `@sizeOf(Material) == 52` and `max_materials == 4096`, the material
|
||||||
/// storage buffer should take 208 kiB in VRAM.
|
/// storage buffer should take 208 kiB in VRAM.
|
||||||
@@ -77,43 +80,43 @@ pub fn init() !*Materials {
|
|||||||
|
|
||||||
materials.* = .{
|
materials.* = .{
|
||||||
.map = .empty,
|
.map = .empty,
|
||||||
|
.array = .empty,
|
||||||
.material_buffer = undefined,
|
.material_buffer = undefined,
|
||||||
.material_count = 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try materials.map.ensureTotalCapacity(allocator_persistent, max_materials);
|
try materials.map.ensureTotalCapacity(allocator_persistent, max_materials);
|
||||||
|
try materials.array.ensureTotalCapacityPrecise(allocator_persistent, max_materials);
|
||||||
|
|
||||||
materials.material_buffer = try .init(.{
|
materials.material_buffer = try .init(.{
|
||||||
.usage = .storage,
|
.usage = .storage,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
.array_capacity = max_materials,
|
.capacity = max_materials,
|
||||||
.name = "Materials",
|
.name = "Materials",
|
||||||
});
|
});
|
||||||
errdefer materials.material_buffer.deinit();
|
errdefer materials.material_buffer.deinit();
|
||||||
|
|
||||||
// VOLATILE Synchronize with explicit values on top of `Id` type.
|
// VOLATILE Synchronize with explicit values on top of `Id` type.
|
||||||
|
|
||||||
try materials.material_buffer.write(.{
|
const material_data = [_]shaders.Material{
|
||||||
.element_offset = Id.empty.toInt(),
|
.{
|
||||||
.elements = &.{
|
.base_color = .zero,
|
||||||
.{
|
.emissive = .init(1, 0, 1),
|
||||||
.base_color = .zero,
|
.ior = 1.45,
|
||||||
.emissive = .init(1, 0, 1),
|
.metallic = 0,
|
||||||
.ior = 1.45,
|
.normal_scale = 1,
|
||||||
.metallic = 0,
|
.occlusion_texture_strength = 1,
|
||||||
.normal_scale = 1,
|
.roughness = 1,
|
||||||
.occlusion_texture_strength = 1,
|
.base_color_texture = .empty_base_color,
|
||||||
.roughness = 1,
|
.emissive_texture = .empty_emissive,
|
||||||
.base_color_texture = .empty_base_color,
|
.normal_texture = .empty_normal,
|
||||||
.emissive_texture = .empty_emissive,
|
.occlusion_roughness_metallic_texture = .empty_occlusion_roughness_metallic,
|
||||||
.normal_texture = .empty_normal,
|
|
||||||
.occlusion_roughness_metallic_texture = .empty_occlusion_roughness_metallic,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
materials.material_count += 1;
|
|
||||||
|
|
||||||
std.debug.assert(materials.material_count == @typeInfo(Id).@"enum".fields.len);
|
materials.array.appendSliceAssumeCapacity(&material_data);
|
||||||
|
try materials.material_buffer.writeSlice(&material_data);
|
||||||
|
|
||||||
|
std.debug.assert(materials.array.items.len == @typeInfo(Id).@"enum".fields.len);
|
||||||
return materials;
|
return materials;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,13 +179,13 @@ pub fn getOrLoad(self: *Materials, maybe_filename: ?[]const u8) !Id {
|
|||||||
if (self.map.get(key)) |id| {
|
if (self.map.get(key)) |id| {
|
||||||
return id;
|
return id;
|
||||||
} else {
|
} else {
|
||||||
const id = Id.fromIndexSafe(self.material_count) catch |err| switch (err) {
|
const id = Id.fromIndexSafe(self.array.items.len) catch |err| switch (err) {
|
||||||
error.Overflow => return error.OutOfMaterials,
|
error.Overflow => return error.OutOfMaterials,
|
||||||
};
|
};
|
||||||
try self.loadMaterial(filename, id.toInt());
|
const material = try self.loadMaterial(filename, id.toInt());
|
||||||
|
|
||||||
self.map.putAssumeCapacityNoClobber(key, id);
|
self.map.putAssumeCapacityNoClobber(key, id);
|
||||||
self.material_count += 1;
|
self.array.appendAssumeCapacity(material);
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -214,13 +217,13 @@ pub fn getOrLoadAtom(self: *Materials, filename: Atom) !Id {
|
|||||||
if (self.map.get(key)) |id| {
|
if (self.map.get(key)) |id| {
|
||||||
return id;
|
return id;
|
||||||
} else {
|
} else {
|
||||||
const id = Id.fromIndexSafe(self.material_count) catch |err| switch (err) {
|
const id = Id.fromIndexSafe(self.array.items.len) catch |err| switch (err) {
|
||||||
error.Overflow => return error.OutOfMaterials,
|
error.Overflow => return error.OutOfMaterials,
|
||||||
};
|
};
|
||||||
try self.loadMaterial(filename.toString(), id.toInt());
|
const material = try self.loadMaterial(filename.toString(), id.toInt());
|
||||||
|
|
||||||
self.map.putAssumeCapacityNoClobber(key, id);
|
self.map.putAssumeCapacityNoClobber(key, id);
|
||||||
self.material_count += 1;
|
self.array.appendAssumeCapacity(material);
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -266,7 +269,7 @@ pub fn loadAll(self: *Materials) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadMaterial(self: *Materials, filename: []const u8, index: u32) !void {
|
fn loadMaterial(self: *Materials, filename: []const u8, index: u32) !shaders.Material {
|
||||||
const allocator_frame = ctx.allocator_frame;
|
const allocator_frame = ctx.allocator_frame;
|
||||||
const io = ctx.io;
|
const io = ctx.io;
|
||||||
const textures = ctx.textures;
|
const textures = ctx.textures;
|
||||||
@@ -307,22 +310,20 @@ fn loadMaterial(self: *Materials, filename: []const u8, index: u32) !void {
|
|||||||
.allocate = .alloc_if_needed,
|
.allocate = .alloc_if_needed,
|
||||||
});
|
});
|
||||||
|
|
||||||
try self.material_buffer.write(.{
|
const material_data: shaders.Material = .{
|
||||||
.element_offset = index,
|
.base_color = .initArray(material_json.baseColor),
|
||||||
.elements = &.{
|
.emissive = .initArray(material_json.emissive),
|
||||||
.{
|
.ior = material_json.ior,
|
||||||
.base_color = .initArray(material_json.baseColor),
|
.metallic = material_json.metallic,
|
||||||
.emissive = .initArray(material_json.emissive),
|
.normal_scale = material_json.normalScale,
|
||||||
.ior = material_json.ior,
|
.occlusion_texture_strength = material_json.occlusionTextureStrength,
|
||||||
.metallic = material_json.metallic,
|
.roughness = material_json.roughness,
|
||||||
.normal_scale = material_json.normalScale,
|
.base_color_texture = try textures.getOrLoad(material_json.baseColorTexture, .base_color),
|
||||||
.occlusion_texture_strength = material_json.occlusionTextureStrength,
|
.emissive_texture = try textures.getOrLoad(material_json.emissiveTexture, .emissive),
|
||||||
.roughness = material_json.roughness,
|
.normal_texture = try textures.getOrLoad(material_json.normalTexture, .normal),
|
||||||
.base_color_texture = try textures.getOrLoad(material_json.baseColorTexture, .base_color),
|
.occlusion_roughness_metallic_texture = try textures.getOrLoad(material_json.occlusionRoughnessMetallicTexture, .occlusion_roughness_metallic),
|
||||||
.emissive_texture = try textures.getOrLoad(material_json.emissiveTexture, .emissive),
|
};
|
||||||
.normal_texture = try textures.getOrLoad(material_json.normalTexture, .normal),
|
|
||||||
.occlusion_roughness_metallic_texture = try textures.getOrLoad(material_json.occlusionRoughnessMetallicTexture, .occlusion_roughness_metallic),
|
try self.material_buffer.writeAt(index, material_data);
|
||||||
},
|
return material_data;
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ const vm = @import("vecmath");
|
|||||||
const CommandBuffer = @import("CommandBuffer.zig");
|
const CommandBuffer = @import("CommandBuffer.zig");
|
||||||
const Engine = @import("Engine.zig");
|
const Engine = @import("Engine.zig");
|
||||||
const GenericBuffer = @import("GenericBuffer.zig").GenericBuffer;
|
const GenericBuffer = @import("GenericBuffer.zig").GenericBuffer;
|
||||||
const StagingBuffer = @import("StagingBuffer.zig");
|
|
||||||
const Swapchain = @import("Swapchain.zig");
|
const Swapchain = @import("Swapchain.zig");
|
||||||
const Texture = @import("Texture.zig");
|
const Texture = @import("Texture.zig");
|
||||||
|
|
||||||
@@ -18,7 +17,7 @@ image: vk.Image,
|
|||||||
image_view: vk.ImageView,
|
image_view: vk.ImageView,
|
||||||
device_memory: vk.DeviceMemory,
|
device_memory: vk.DeviceMemory,
|
||||||
|
|
||||||
vertex_buffer: GenericBuffer(void, vm.Vector3),
|
vertex_buffer: GenericBuffer(vm.Vector3),
|
||||||
index_buffer: shaders.IndexBuffer,
|
index_buffer: shaders.IndexBuffer,
|
||||||
sampler: vk.Sampler,
|
sampler: vk.Sampler,
|
||||||
|
|
||||||
@@ -69,15 +68,21 @@ pub fn load(
|
|||||||
|
|
||||||
// --- LOAD IMAGE INTO STAGING BUFFER --------------------------------------
|
// --- LOAD IMAGE INTO STAGING BUFFER --------------------------------------
|
||||||
|
|
||||||
var staging_buffer = try StagingBuffer.init(.{
|
var staging_buffer = try GenericBuffer(vm.ColorHdr).init(.{
|
||||||
.capacity = @intCast(img.width * img.height * @sizeOf(vm.ColorHdr)),
|
.usage = .staging,
|
||||||
.target_queue = .compute,
|
.target_queue = .compute,
|
||||||
|
.mappable = true,
|
||||||
|
.capacity = img.width * img.height,
|
||||||
});
|
});
|
||||||
defer staging_buffer.deinit();
|
defer staging_buffer.deinit();
|
||||||
|
|
||||||
const staging_memory = try staging_buffer.map();
|
{
|
||||||
@memcpy(staging_memory, @as([*]const u8, @ptrCast(img.data)));
|
const staging_memory = try staging_buffer.mapSlice();
|
||||||
staging_buffer.unmap();
|
defer staging_buffer.unmap();
|
||||||
|
|
||||||
|
@memcpy(staging_memory, img.data);
|
||||||
|
try staging_buffer.flush(0, img.width * img.height);
|
||||||
|
}
|
||||||
|
|
||||||
// --- CREATE EQUIRECTANGULAR IMAGE ----------------------------------------
|
// --- CREATE EQUIRECTANGULAR IMAGE ----------------------------------------
|
||||||
|
|
||||||
@@ -261,6 +266,26 @@ pub fn load(
|
|||||||
});
|
});
|
||||||
errdefer engine.destroyImageView(cubemap_image_view);
|
errdefer engine.destroyImageView(cubemap_image_view);
|
||||||
|
|
||||||
|
// NOTE Because slang cannot conceive of such a thing as RWTextureCube
|
||||||
|
// (equivalent to imageCube in GLSL, which docs claim exists, but it
|
||||||
|
// doesn't), we have to resort to RWTexture2DArray (WTexture2DArray to be
|
||||||
|
// specific) in the compute shader. We have to make another image view with
|
||||||
|
// appropriate view_type to pass validation.
|
||||||
|
|
||||||
|
const cubemap_image_view_2d_array = try engine.createImageView(.{
|
||||||
|
.image = cubemap_image,
|
||||||
|
.view_type = .@"2d_array",
|
||||||
|
.format = .r16g16b16a16_sfloat,
|
||||||
|
.subresource_range = .{
|
||||||
|
.aspect_mask = .{ .color_bit = true },
|
||||||
|
.base_mip_level = 0,
|
||||||
|
.level_count = 1,
|
||||||
|
.base_array_layer = 0,
|
||||||
|
.layer_count = 6,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
defer engine.destroyImageView(cubemap_image_view_2d_array);
|
||||||
|
|
||||||
// --- PIPELINE AND DESCRIPTORS --------------------------------------------
|
// --- PIPELINE AND DESCRIPTORS --------------------------------------------
|
||||||
|
|
||||||
const sampler = try engine.createSampler(.{
|
const sampler = try engine.createSampler(.{
|
||||||
@@ -312,7 +337,7 @@ pub fn load(
|
|||||||
});
|
});
|
||||||
defer engine.destroyPipelineLayout(compute_pipeline_layout);
|
defer engine.destroyPipelineLayout(compute_pipeline_layout);
|
||||||
|
|
||||||
const compute_shader = try engine.createShaderModule(.{ .code = &shaders.equirect_to_cube_comp_spv });
|
const compute_shader = try engine.createShaderModule(.{ .code = &shaders.equirect_to_cube_spv });
|
||||||
defer engine.destroyShaderModule(compute_shader);
|
defer engine.destroyShaderModule(compute_shader);
|
||||||
|
|
||||||
var compute_pipeline: vk.Pipeline = undefined;
|
var compute_pipeline: vk.Pipeline = undefined;
|
||||||
@@ -380,7 +405,7 @@ pub fn load(
|
|||||||
.image = &.{
|
.image = &.{
|
||||||
.{
|
.{
|
||||||
.sampler = .null_handle,
|
.sampler = .null_handle,
|
||||||
.image_view = cubemap_image_view,
|
.image_view = cubemap_image_view_2d_array,
|
||||||
.image_layout = .general,
|
.image_layout = .general,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -475,10 +500,10 @@ pub fn load(
|
|||||||
|
|
||||||
// --- SKYBOX PIPELINE -----------------------------------------------------
|
// --- SKYBOX PIPELINE -----------------------------------------------------
|
||||||
|
|
||||||
var vertex_buffer = try GenericBuffer(void, vm.Vector3).init(.{
|
var vertex_buffer = try GenericBuffer(vm.Vector3).init(.{
|
||||||
.usage = .vertex,
|
.usage = .vertex,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
.array_capacity = 8,
|
.capacity = 8,
|
||||||
});
|
});
|
||||||
errdefer vertex_buffer.deinit();
|
errdefer vertex_buffer.deinit();
|
||||||
|
|
||||||
@@ -490,47 +515,43 @@ pub fn load(
|
|||||||
// ┃╱ ┃╱ │╱
|
// ┃╱ ┃╱ │╱
|
||||||
// 0━━━━1 O────X
|
// 0━━━━1 O────X
|
||||||
|
|
||||||
try vertex_buffer.write(.{
|
try vertex_buffer.writeSlice(&.{
|
||||||
.elements = &.{
|
.init(-1, -1, -1),
|
||||||
.init(-1, -1, -1),
|
.init(1, -1, -1),
|
||||||
.init(1, -1, -1),
|
.init(-1, 1, -1),
|
||||||
.init(-1, 1, -1),
|
.init(1, 1, -1),
|
||||||
.init(1, 1, -1),
|
.init(-1, -1, 1),
|
||||||
.init(-1, -1, 1),
|
.init(1, -1, 1),
|
||||||
.init(1, -1, 1),
|
.init(-1, 1, 1),
|
||||||
.init(-1, 1, 1),
|
.init(1, 1, 1),
|
||||||
.init(1, 1, 1),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var index_buffer = try GenericBuffer(void, u16).init(.{
|
var index_buffer: shaders.IndexBuffer = try .init(.{
|
||||||
.usage = .index,
|
.usage = .index,
|
||||||
.target_queue = .graphics,
|
.target_queue = .graphics,
|
||||||
.array_capacity = 36,
|
.capacity = 36,
|
||||||
});
|
});
|
||||||
errdefer index_buffer.deinit();
|
errdefer index_buffer.deinit();
|
||||||
|
|
||||||
try index_buffer.write(.{
|
try index_buffer.writeSlice(&.{
|
||||||
.elements = &.{
|
// Positive X
|
||||||
// Positive X
|
3, 1, 7,
|
||||||
3, 1, 7,
|
7, 1, 5,
|
||||||
7, 1, 5,
|
// Negative X
|
||||||
// Negative X
|
0, 2, 4,
|
||||||
0, 2, 4,
|
4, 2, 6,
|
||||||
4, 2, 6,
|
// Positive Y
|
||||||
// Positive Y
|
2, 3, 6,
|
||||||
2, 3, 6,
|
6, 3, 7,
|
||||||
6, 3, 7,
|
// Negative Y
|
||||||
// Negative Y
|
1, 0, 5,
|
||||||
1, 0, 5,
|
5, 0, 4,
|
||||||
5, 0, 4,
|
// Positive Z
|
||||||
// Positive Z
|
6, 7, 4,
|
||||||
6, 7, 4,
|
4, 7, 5,
|
||||||
4, 7, 5,
|
// Negative Z
|
||||||
// Negative Z
|
0, 1, 2,
|
||||||
0, 1, 2,
|
2, 1, 3,
|
||||||
2, 1, 3,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const descriptor_set_layout = try engine.createDescriptorSetLayout(.{
|
const descriptor_set_layout = try engine.createDescriptorSetLayout(.{
|
||||||
@@ -622,23 +643,20 @@ pub fn load(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const vertex_shader = try engine.createShaderModule(.{ .code = &shaders.skybox_vert_spv });
|
const shader = try engine.createShaderModule(.{ .code = &shaders.skybox_spv });
|
||||||
defer engine.destroyShaderModule(vertex_shader);
|
defer engine.destroyShaderModule(shader);
|
||||||
|
|
||||||
const fragment_shader = try engine.createShaderModule(.{ .code = &shaders.skybox_frag_spv });
|
|
||||||
defer engine.destroyShaderModule(fragment_shader);
|
|
||||||
|
|
||||||
const pipeline = try engine.createGraphicsPipeline(.{
|
const pipeline = try engine.createGraphicsPipeline(.{
|
||||||
.stages = &.{
|
.stages = &.{
|
||||||
.{
|
.{
|
||||||
.stage = .{ .vertex_bit = true },
|
.stage = .{ .vertex_bit = true },
|
||||||
.module = vertex_shader,
|
.module = shader,
|
||||||
.name = "main",
|
.name = "vertexMain",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.stage = .{ .fragment_bit = true },
|
.stage = .{ .fragment_bit = true },
|
||||||
.module = fragment_shader,
|
.module = shader,
|
||||||
.name = "main",
|
.name = "fragmentMain",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.vertex_input_state = .{
|
.vertex_input_state = .{
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
const StagingBuffer = @This();
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const ctx = @import("../AppContext.zig");
|
|
||||||
const vk = @import("vulkan");
|
|
||||||
|
|
||||||
const Engine = @import("Engine.zig");
|
|
||||||
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
|
|
||||||
|
|
||||||
buffer: vk.Buffer,
|
|
||||||
device_memory: vk.DeviceMemory,
|
|
||||||
capacity: u32,
|
|
||||||
|
|
||||||
pub const InitInfo = struct {
|
|
||||||
target_queue: TargetQueue,
|
|
||||||
capacity: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn init(init_info: InitInfo) !StagingBuffer {
|
|
||||||
const engine = ctx.engine;
|
|
||||||
|
|
||||||
const target_queue_family = switch (init_info.target_queue) {
|
|
||||||
.graphics => engine.graphics_queue.allocation.family,
|
|
||||||
.compute => engine.compute_queue.allocation.family,
|
|
||||||
};
|
|
||||||
const transfer_queue_family = engine.transfer_queue.allocation.family;
|
|
||||||
|
|
||||||
const buffer = try engine.createBuffer(.{
|
|
||||||
.size = init_info.capacity,
|
|
||||||
.usage = .{
|
|
||||||
.transfer_src_bit = true,
|
|
||||||
},
|
|
||||||
.queue_family_indices = &.{ target_queue_family, transfer_queue_family },
|
|
||||||
});
|
|
||||||
errdefer engine.destroyBuffer(buffer);
|
|
||||||
|
|
||||||
const memory_requirements = engine.getBufferMemoryRequirements(buffer);
|
|
||||||
const device_memory = try engine.allocate(
|
|
||||||
memory_requirements,
|
|
||||||
.{
|
|
||||||
.host_visible_bit = true,
|
|
||||||
.host_coherent_bit = true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
errdefer engine.freeMemory(device_memory);
|
|
||||||
|
|
||||||
try engine.bindBufferMemory(buffer, device_memory, 0);
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.buffer = buffer,
|
|
||||||
.device_memory = device_memory,
|
|
||||||
.capacity = init_info.capacity,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *StagingBuffer) void {
|
|
||||||
const engine = ctx.engine;
|
|
||||||
|
|
||||||
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
|
|
||||||
|
|
||||||
engine.freeMemory(self.device_memory);
|
|
||||||
engine.destroyBuffer(self.buffer);
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn map(self: StagingBuffer) ![]u8 {
|
|
||||||
const mapped_memory = try self.mapPartial(0, self.capacity);
|
|
||||||
return mapped_memory;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mapPartial(self: StagingBuffer, offset: u32, len: u32) ![]u8 {
|
|
||||||
const engine = ctx.engine;
|
|
||||||
|
|
||||||
const mapped_memory = try engine.mapMemory(self.device_memory, offset, len, .{});
|
|
||||||
return mapped_memory;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unmap(self: StagingBuffer) void {
|
|
||||||
const engine = ctx.engine;
|
|
||||||
|
|
||||||
engine.unmapMemory(self.device_memory);
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ const vk = @import("vulkan");
|
|||||||
const CommandBuffer = @import("CommandBuffer.zig");
|
const CommandBuffer = @import("CommandBuffer.zig");
|
||||||
const DeviceAllocation = @import("DeviceAllocation.zig");
|
const DeviceAllocation = @import("DeviceAllocation.zig");
|
||||||
const Engine = @import("Engine.zig");
|
const Engine = @import("Engine.zig");
|
||||||
const StagingBuffer = @import("StagingBuffer.zig");
|
const GenericBuffer = @import("GenericBuffer.zig").GenericBuffer;
|
||||||
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
|
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
|
||||||
|
|
||||||
pub const Usage = enum {
|
pub const Usage = enum {
|
||||||
@@ -207,15 +207,21 @@ pub fn writeRaw(self: Texture, data: []const u8) !void {
|
|||||||
std.debug.assert(data.len == byte_length);
|
std.debug.assert(data.len == byte_length);
|
||||||
std.debug.assert(self.usage != .depth);
|
std.debug.assert(self.usage != .depth);
|
||||||
|
|
||||||
var staging_buffer = try StagingBuffer.init(.{
|
var staging_buffer = try GenericBuffer(u8).init(.{
|
||||||
.capacity = @intCast(byte_length),
|
.usage = .staging,
|
||||||
.target_queue = self.target_queue,
|
.target_queue = self.target_queue,
|
||||||
|
.mappable = true,
|
||||||
|
.capacity = @intCast(byte_length),
|
||||||
});
|
});
|
||||||
defer staging_buffer.deinit();
|
defer staging_buffer.deinit();
|
||||||
|
|
||||||
const staging_memory = try staging_buffer.map();
|
{
|
||||||
@memcpy(staging_memory, data);
|
const staging_memory = try staging_buffer.mapSlice();
|
||||||
staging_buffer.unmap();
|
defer staging_buffer.unmap();
|
||||||
|
|
||||||
|
@memcpy(staging_memory, data);
|
||||||
|
try staging_buffer.flush(0, byte_length);
|
||||||
|
}
|
||||||
|
|
||||||
// --- TRANSITION TO TRANSFER_DST_OPTIMAL AND COPY -----------------
|
// --- TRANSITION TO TRANSFER_DST_OPTIMAL AND COPY -----------------
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
ctx.stbi = try ctx.allocator_persistent.create(media.stbi);
|
ctx.stbi = try ctx.allocator_persistent.create(media.stbi);
|
||||||
ctx.stbi.* = .init(ctx.allocator_general, ctx.io);
|
ctx.stbi.* = .init(ctx.allocator_general, ctx.io);
|
||||||
defer ctx.stbi.deinit();
|
defer ctx.stbi.deinit();
|
||||||
|
ctx.stbtt = try ctx.allocator_persistent.create(media.stbtt);
|
||||||
|
ctx.stbtt.* = .init(ctx.allocator_general, ctx.io);
|
||||||
|
defer ctx.stbtt.deinit();
|
||||||
|
|
||||||
// --- INITIALIZE APP CONTEXT ---
|
// --- INITIALIZE APP CONTEXT ---
|
||||||
|
|
||||||
@@ -105,6 +108,12 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
ctx.materials = undefined;
|
ctx.materials = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.fonts = try .init();
|
||||||
|
defer {
|
||||||
|
ctx.fonts.deinit();
|
||||||
|
ctx.fonts = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.gui = try .init();
|
ctx.gui = try .init();
|
||||||
defer {
|
defer {
|
||||||
ctx.gui.deinit();
|
ctx.gui.deinit();
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ const Materials = @import("engine/Materials.zig");
|
|||||||
const GenericBuffer = @import("engine/GenericBuffer.zig").GenericBuffer;
|
const GenericBuffer = @import("engine/GenericBuffer.zig").GenericBuffer;
|
||||||
const Textures = @import("engine/Textures.zig");
|
const Textures = @import("engine/Textures.zig");
|
||||||
|
|
||||||
pub const VertexBuffer = GenericBuffer(void, Vertex);
|
pub const VertexBuffer = GenericBuffer(Vertex);
|
||||||
pub const IndexBuffer = GenericBuffer(void, Index);
|
pub const IndexBuffer = GenericBuffer(Index);
|
||||||
pub const GlobalUniformsBuffer = GenericBuffer(GlobalUniforms, void);
|
pub const GlobalUniformsBuffer = GenericBuffer(GlobalUniforms);
|
||||||
pub const PointLightBuffer = GenericBuffer(u32, PointLight);
|
pub const PointLightBuffer = GenericBuffer(PointLight);
|
||||||
pub const DirectionalLightBuffer = GenericBuffer(u32, DirectionalLight);
|
pub const DirectionalLightBuffer = GenericBuffer(DirectionalLight);
|
||||||
pub const MaterialBuffer = GenericBuffer(void, Material);
|
pub const MaterialBuffer = GenericBuffer(Material);
|
||||||
pub const ObjectUniformsBuffer = GenericBuffer(void, ObjectUniforms);
|
pub const ObjectUniformsBuffer = GenericBuffer(ObjectUniforms);
|
||||||
|
|
||||||
pub const Vertex = extern struct {
|
pub const Vertex = extern struct {
|
||||||
positionOS: vm.Vector3,
|
positionOS: vm.Vector3,
|
||||||
@@ -44,9 +44,12 @@ pub const Vertex = extern struct {
|
|||||||
pub const Index = u16;
|
pub const Index = u16;
|
||||||
|
|
||||||
pub const GlobalUniforms = extern struct {
|
pub const GlobalUniforms = extern struct {
|
||||||
matrixWStoVS: vm.Matrix4x4,
|
matrixWStoVS: [4]vm.Vector3,
|
||||||
matrixVStoCS: vm.Matrix4x4,
|
matrixVStoCS: vm.Matrix4x4,
|
||||||
matrixSSPXtoCS: vm.Matrix3x2,
|
matrixSSPXtoCS: vm.Matrix3x2,
|
||||||
|
|
||||||
|
pointLightCount: u32,
|
||||||
|
directionalLightCount: u32,
|
||||||
ambientLight: vm.Vector3,
|
ambientLight: vm.Vector3,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,16 +93,24 @@ pub const Material = extern struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const ObjectUniforms = extern struct {
|
pub const ObjectUniforms = extern struct {
|
||||||
matrixOStoWS: vm.Matrix4x4,
|
// Limited to a composition of translation and rotation
|
||||||
matrixOStoWSNormal: vm.Matrix4x4,
|
matrixOStoWS: [4]vm.Vector3,
|
||||||
|
texCoordTransform: vm.Matrix3x2,
|
||||||
material: Materials.Id,
|
material: Materials.Id,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const equirect_to_cube_comp_spv align(4) = @embedFile("shaders/equirect_to_cube_comp.spv").*;
|
pub const GlyphData = extern struct {
|
||||||
pub const gui_box_vert_spv align(4) = @embedFile("shaders/gui_box_vert.spv").*;
|
bottomLeftEM: vm.Vector2,
|
||||||
pub const gui_box_frag_spv align(4) = @embedFile("shaders/gui_box_frag.spv").*;
|
sizeEM: vm.Vector2,
|
||||||
pub const main_vert_spv align(4) = @embedFile("shaders/main_vert.spv").*;
|
verticalBandPtr: u32,
|
||||||
pub const main_frag_spv align(4) = @embedFile("shaders/main_frag.spv").*;
|
horizontalBandPtr: u32,
|
||||||
pub const skybox_vert_spv align(4) = @embedFile("shaders/skybox_vert.spv").*;
|
verticalBandCount: u32,
|
||||||
pub const skybox_frag_spv align(4) = @embedFile("shaders/skybox_frag.spv").*;
|
horizontalBandCount: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const equirect_to_cube_spv align(4) = @embedFile("shaders/equirect_to_cube").*;
|
||||||
|
pub const gui_box_spv align(4) = @embedFile("shaders/gui_box").*;
|
||||||
|
pub const gui_text_spv align(4) = @embedFile("shaders/gui_text").*;
|
||||||
|
pub const gui_image_spv align(4) = @embedFile("shaders/gui_image").*;
|
||||||
|
pub const main_spv align(4) = @embedFile("shaders/main").*;
|
||||||
|
pub const skybox_spv align(4) = @embedFile("shaders/skybox").*;
|
||||||
|
|||||||
@@ -59,6 +59,49 @@ pub const Transform = enum(u3) {
|
|||||||
mirror_rotate_cw = 5,
|
mirror_rotate_cw = 5,
|
||||||
mirror_rotate_ccw = 6,
|
mirror_rotate_ccw = 6,
|
||||||
mirror_rotate_180 = 7,
|
mirror_rotate_180 = 7,
|
||||||
|
|
||||||
|
pub fn getMatrix(self: Transform) vm.Matrix3x2 {
|
||||||
|
return switch (self) {
|
||||||
|
// zig fmt: off
|
||||||
|
.identity => .identity,
|
||||||
|
.rotate_cw => .init(
|
||||||
|
0, -1,
|
||||||
|
1, 0,
|
||||||
|
0, 1,
|
||||||
|
),
|
||||||
|
.rotate_ccw => .init(
|
||||||
|
0, 1,
|
||||||
|
-1, 0,
|
||||||
|
1, 0,
|
||||||
|
),
|
||||||
|
.rotate_180 => .init(
|
||||||
|
-1, 0,
|
||||||
|
0, -1,
|
||||||
|
1, 1,
|
||||||
|
),
|
||||||
|
.mirror => .init(
|
||||||
|
-1, 0,
|
||||||
|
0, 1,
|
||||||
|
1, 0,
|
||||||
|
),
|
||||||
|
.mirror_rotate_cw => .init(
|
||||||
|
0, -1,
|
||||||
|
-1, 0,
|
||||||
|
1, 1,
|
||||||
|
),
|
||||||
|
.mirror_rotate_ccw => .init(
|
||||||
|
0, 1,
|
||||||
|
1, 0,
|
||||||
|
0, 0,
|
||||||
|
),
|
||||||
|
.mirror_rotate_180 => .init(
|
||||||
|
0, 1,
|
||||||
|
-1, 0,
|
||||||
|
1, 0,
|
||||||
|
),
|
||||||
|
// zig fmt: on
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ┌──────────────────── x
|
// ┌──────────────────── x
|
||||||
|
|||||||
Reference in New Issue
Block a user