mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-11 22:50:08 +00:00
+2
-1
@@ -22,7 +22,8 @@ members = [
|
||||
"fyrox-texture",
|
||||
"fyrox-autotile",
|
||||
"fyrox-material",
|
||||
"fyrox-graphics-gl"]
|
||||
"fyrox-graphics-gl",
|
||||
"fyrox-graphics-wgpu"]
|
||||
resolver = "2"
|
||||
|
||||
[profile.editor-standalone]
|
||||
|
||||
@@ -13,6 +13,11 @@ readme = "README.md"
|
||||
include = ["/src/**/*", "/Cargo.toml", "/LICENSE", "/README.md"]
|
||||
|
||||
[dependencies]
|
||||
fyrox = { version = "2.0.0-rc.1", path = "../fyrox" }
|
||||
fyroxed_base = { version = "2.0.0-rc.1", path = "../editor" }
|
||||
fyrox = { version = "2.0.0-rc.1", path = "../fyrox", default-features = false }
|
||||
fyroxed_base = { version = "2.0.0-rc.1", path = "../editor", default-features = false }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
|
||||
[features]
|
||||
default = ["backend_opengl"]
|
||||
backend_opengl = ["fyrox/backend_opengl", "fyroxed_base/backend_opengl"]
|
||||
backend_wgpu = ["fyrox/backend_wgpu", "fyroxed_base/backend_wgpu"]
|
||||
|
||||
+3
-1
@@ -32,5 +32,7 @@ notify = "8"
|
||||
bitflags = "2.9.1"
|
||||
|
||||
[features]
|
||||
default = ["fyrox/default"]
|
||||
default = ["fyrox/default", "backend_opengl"]
|
||||
dylib_engine = ["fyrox/dylib"]
|
||||
backend_opengl = ["fyrox/backend_opengl"]
|
||||
backend_wgpu = ["fyrox/backend_wgpu"]
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
(
|
||||
name: "GizmoShader",
|
||||
|
||||
resources: [
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(
|
||||
name: "diffuseColor",
|
||||
kind: Color(r: 255, g: 255, b: 255, a: 255),
|
||||
),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "fyrox_instanceData",
|
||||
kind: PropertyGroup([
|
||||
// Autogenerated
|
||||
]),
|
||||
binding: 1
|
||||
),
|
||||
],
|
||||
|
||||
disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"],
|
||||
|
||||
passes: [
|
||||
(
|
||||
name: "Forward",
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: Some(Less),
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: SrcAlpha,
|
||||
dfactor: OneMinusSrcAlpha,
|
||||
alpha_sfactor: SrcAlpha,
|
||||
alpha_dfactor: OneMinusSrcAlpha,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.position = fyrox_instanceData.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
struct FragOutput {
|
||||
@location(0) color: vec4f,
|
||||
@builtin(frag_depth) depth: f32,
|
||||
};
|
||||
|
||||
@fragment
|
||||
fn fs_main(@builtin(position) fragCoord: vec4f) -> FragOutput {
|
||||
var output: FragOutput;
|
||||
output.color = properties.diffuseColor;
|
||||
|
||||
// Pull depth towards near clipping plane so the gizmo will be drawn on top
|
||||
// of everything, but its parts will be correctly handled by depth test.
|
||||
output.depth = fragCoord.z * 0.001;
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,215 @@
|
||||
(
|
||||
name: "GridShader",
|
||||
|
||||
resources: [
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(
|
||||
name: "diffuseColor",
|
||||
kind: Color(r: 40, g: 40, b: 40, a: 255),
|
||||
),
|
||||
(
|
||||
name: "xAxisColor",
|
||||
kind: Color(r: 255, g: 0, b: 0, a: 255),
|
||||
),
|
||||
(
|
||||
name: "yAxisColor",
|
||||
kind: Color(r: 0, g: 255, b: 0, a: 255),
|
||||
),
|
||||
(
|
||||
name: "zAxisColor",
|
||||
kind: Color(r: 0, g: 0, b: 255, a: 255),
|
||||
),
|
||||
(
|
||||
name: "orientation",
|
||||
kind: Int(value: 0),
|
||||
),
|
||||
(
|
||||
name: "isPerspective",
|
||||
kind: Bool(value: false)
|
||||
),
|
||||
(
|
||||
name: "scale",
|
||||
kind: Vector2(value: (1.0, 1.0))
|
||||
)
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "fyrox_cameraData",
|
||||
kind: PropertyGroup([
|
||||
// Autogenerated
|
||||
]),
|
||||
binding: 1
|
||||
),
|
||||
],
|
||||
|
||||
disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"],
|
||||
|
||||
passes: [
|
||||
(
|
||||
name: "Forward",
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: Some(Less),
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: SrcAlpha,
|
||||
dfactor: OneMinusSrcAlpha,
|
||||
alpha_sfactor: SrcAlpha,
|
||||
alpha_dfactor: OneMinusSrcAlpha,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) nearPoint: vec3f,
|
||||
@location(1) farPoint: vec3f,
|
||||
};
|
||||
|
||||
fn Unproject(x: f32, y: f32, z: f32, matrix: mat4x4f) -> vec3f {
|
||||
var position = matrix * vec4f(x, y, z, 1.0);
|
||||
return position.xyz / position.w;
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
var invViewProj = inverse_mat4(fyrox_cameraData.viewProjectionMatrix);
|
||||
output.nearPoint = Unproject(input.vertexPosition.x, input.vertexPosition.y, 0.0, invViewProj);
|
||||
output.farPoint = Unproject(input.vertexPosition.x, input.vertexPosition.y, 1.0, invViewProj);
|
||||
output.position = vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
// Original code: https://asliceofrendering.com/scene%20helper/2020/01/05/InfiniteGrid/
|
||||
// Fixed and adapted for Fyrox.
|
||||
|
||||
struct FragOutput {
|
||||
@location(0) color: vec4f,
|
||||
@builtin(frag_depth) depth: f32,
|
||||
};
|
||||
|
||||
fn grid(fragPos3D: vec3f) -> vec4f {
|
||||
var projection: vec2f;
|
||||
var planeNormal: vec3f;
|
||||
var xColor: vec4f;
|
||||
var yColor: vec4f;
|
||||
if (properties.orientation == 0) {
|
||||
projection = fragPos3D.xz;
|
||||
planeNormal = vec3f(0.0, 1.0, 0.0);
|
||||
xColor = properties.xAxisColor;
|
||||
yColor = properties.zAxisColor;
|
||||
} else if (properties.orientation == 1) {
|
||||
projection = fragPos3D.xy;
|
||||
planeNormal = vec3f(0.0, 0.0, 1.0);
|
||||
xColor = properties.yAxisColor;
|
||||
yColor = properties.xAxisColor;
|
||||
} else if (properties.orientation == 2) {
|
||||
projection = fragPos3D.zy;
|
||||
planeNormal = vec3f(1.0, 0.0, 0.0);
|
||||
xColor = properties.zAxisColor;
|
||||
yColor = properties.yAxisColor;
|
||||
}
|
||||
|
||||
var coord = projection * properties.scale;
|
||||
var derivative = fwidth(coord);
|
||||
var gridVal = abs(fract(coord - vec2f(0.5)) - vec2f(0.5)) / derivative;
|
||||
var lineVal = min(gridVal.x, gridVal.y);
|
||||
var minX = 0.5 * min(derivative.x, 1.0);
|
||||
var minY = 0.5 * min(derivative.y, 1.0);
|
||||
|
||||
var color = properties.diffuseColor;
|
||||
var alpha = 1.0 - min(lineVal, 1.0);
|
||||
// Sharpen lines a bit.
|
||||
color.a = select(0.0, 1.0, alpha >= 0.5);
|
||||
|
||||
if (projection.x > -minX && projection.x < minX) {
|
||||
color = vec4f(xColor.xyz, color.a);
|
||||
} else if (projection.y > -minY && projection.y < minY) {
|
||||
color = vec4f(yColor.xyz, color.a);
|
||||
} else {
|
||||
var viewDir = fragPos3D - fyrox_cameraData.position;
|
||||
// This helps to negate moire pattern at large distances.
|
||||
var cosAngle = abs(dot(planeNormal, normalize(viewDir)));
|
||||
color.a *= cosAngle;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
fn computeDepth(pos: vec3f) -> f32 {
|
||||
var clip_space_pos = fyrox_cameraData.viewProjectionMatrix * vec4f(pos.xyz, 1.0);
|
||||
return (clip_space_pos.z / clip_space_pos.w);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(@location(0) nearPoint: vec3f, @location(1) farPoint: vec3f, @builtin(position) fragCoord: vec4f) -> FragOutput {
|
||||
var nearCoord: f32;
|
||||
var farCoord: f32;
|
||||
if (properties.orientation == 0) {
|
||||
nearCoord = nearPoint.y;
|
||||
farCoord = farPoint.y;
|
||||
} else if (properties.orientation == 1) {
|
||||
nearCoord = nearPoint.z;
|
||||
farCoord = farPoint.z;
|
||||
} else if (properties.orientation == 2) {
|
||||
nearCoord = nearPoint.x;
|
||||
farCoord = farPoint.x;
|
||||
}
|
||||
|
||||
var denominator = farCoord - nearCoord;
|
||||
var t = select(0.0, -nearCoord / denominator, denominator != 0.0);
|
||||
|
||||
var fragPos3D = nearPoint + t * (farPoint - nearPoint);
|
||||
|
||||
var depth = computeDepth(fragPos3D);
|
||||
|
||||
var output: FragOutput;
|
||||
output.depth = depth;
|
||||
|
||||
output.color = grid(fragPos3D);
|
||||
if (properties.isPerspective != 0u) {
|
||||
output.color.a *= f32(t > 0.0);
|
||||
}
|
||||
|
||||
// Alpha test to prevent blending issues.
|
||||
if (output.color.a < 0.01) {
|
||||
discard;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,104 @@
|
||||
(
|
||||
name: "Highlight",
|
||||
resources: [
|
||||
(
|
||||
name: "frameTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "color", kind: Vector4()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: SrcAlpha,
|
||||
dfactor: OneMinusSrcAlpha,
|
||||
alpha_sfactor: SrcAlpha,
|
||||
alpha_dfactor: OneMinusSrcAlpha,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment
|
||||
fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
||||
var size = textureDimensions(frameTexture_tex);
|
||||
|
||||
var w = 1.0 / f32(size.x);
|
||||
var h = 1.0 / f32(size.y);
|
||||
|
||||
var n: array<f32, 9>;
|
||||
n[0] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(-w, -h)).a;
|
||||
n[1] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(0.0, -h)).a;
|
||||
n[2] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(w, -h)).a;
|
||||
n[3] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(-w, 0.0)).a;
|
||||
n[4] = textureSample(frameTexture_tex, frameTexture_samp, texCoord).a;
|
||||
n[5] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(w, 0.0)).a;
|
||||
n[6] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(-w, h)).a;
|
||||
n[7] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(0.0, h)).a;
|
||||
n[8] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(w, h)).a;
|
||||
|
||||
var sobel_edge_h = n[2] + (2.0 * n[5]) + n[8] - (n[0] + (2.0 * n[3]) + n[6]);
|
||||
var sobel_edge_v = n[0] + (2.0 * n[1]) + n[2] - (n[6] + (2.0 * n[7]) + n[8]);
|
||||
var sobel = sqrt((sobel_edge_h * sobel_edge_h) + (sobel_edge_v * sobel_edge_v));
|
||||
|
||||
return vec4f(properties.color.rgb, properties.color.a * sobel);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,90 @@
|
||||
(
|
||||
name: "Overlay",
|
||||
resources: [
|
||||
(
|
||||
name: "diffuseTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "viewProjectionMatrix", kind: Matrix4()),
|
||||
(name: "worldMatrix", kind: Matrix4()),
|
||||
(name: "cameraSideVector", kind: Vector3()),
|
||||
(name: "cameraUpVector", kind: Vector3()),
|
||||
(name: "size", kind: Float()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: SrcAlpha,
|
||||
dfactor: OneMinusSrcAlpha,
|
||||
alpha_sfactor: SrcAlpha,
|
||||
alpha_dfactor: OneMinusSrcAlpha,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
var vertexOffset = input.vertexTexCoord * 2.0 - 1.0;
|
||||
var worldPosition = properties.worldMatrix * vec4f(input.vertexPosition, 1.0);
|
||||
var offset = (vertexOffset.x * properties.cameraSideVector + vertexOffset.y * properties.cameraUpVector) * properties.size;
|
||||
output.position = properties.viewProjectionMatrix * (worldPosition + vec4f(offset.x, offset.y, offset.z, 0.0));
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment
|
||||
fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
||||
return textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
(
|
||||
name: "SpriteGizmoShader",
|
||||
|
||||
resources: [
|
||||
(
|
||||
name: "diffuseTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "fyrox_instanceData",
|
||||
kind: PropertyGroup([
|
||||
// Autogenerated
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "fyrox_cameraData",
|
||||
kind: PropertyGroup([
|
||||
// Autogenerated
|
||||
]),
|
||||
binding: 1
|
||||
),
|
||||
],
|
||||
|
||||
disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"],
|
||||
|
||||
passes: [
|
||||
(
|
||||
name: "Forward",
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: Some(Less),
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: SrcAlpha,
|
||||
dfactor: OneMinusSrcAlpha,
|
||||
alpha_sfactor: SrcAlpha,
|
||||
alpha_dfactor: OneMinusSrcAlpha,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
@location(2) vertexParams: vec2f,
|
||||
@location(3) vertexColor: vec4f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
@location(1) color: vec4f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
var size = input.vertexParams.x;
|
||||
var rotation = input.vertexParams.y;
|
||||
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
output.color = input.vertexColor;
|
||||
var vertexOffset = S_RotateVec2(input.vertexTexCoord * 2.0 - 1.0, rotation);
|
||||
var worldPosition = fyrox_instanceData.worldMatrix * vec4f(input.vertexPosition, 1.0);
|
||||
var offset = (vertexOffset.x * fyrox_cameraData.sideVector + vertexOffset.y * fyrox_cameraData.upVector) * size;
|
||||
output.position = fyrox_cameraData.viewProjectionMatrix * (worldPosition + vec4f(offset.x, offset.y, offset.z, 0.0));
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
struct FragOutput {
|
||||
@location(0) color: vec4f,
|
||||
@builtin(frag_depth) depth: f32,
|
||||
};
|
||||
|
||||
@fragment
|
||||
fn fs_main(@location(0) texCoord: vec2f, @location(1) color: vec4f, @builtin(position) fragCoord: vec4f) -> FragOutput {
|
||||
var output: FragOutput;
|
||||
output.color = color * S_SRGBToLinear(textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord));
|
||||
|
||||
// Pull depth towards near clipping plane so the gizmo will be drawn on top
|
||||
// of everything, but its parts will be correctly handled by depth test.
|
||||
output.depth = fragCoord.z * 0.001;
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
),
|
||||
],
|
||||
)
|
||||
+15
-5
@@ -42,6 +42,12 @@ use crate::{
|
||||
};
|
||||
use std::{any::TypeId, cell::RefCell, rc::Rc};
|
||||
|
||||
#[cfg(feature = "backend_opengl")]
|
||||
const HIGHLIGHT_SHADER_SRC: &str = include_str!("../resources/shaders/opengl/highlight.shader");
|
||||
|
||||
#[cfg(not(feature = "backend_opengl"))]
|
||||
const HIGHLIGHT_SHADER_SRC: &str = include_str!("../resources/shaders/wgpu/highlight.shader");
|
||||
|
||||
pub struct HighlightRenderPass {
|
||||
framebuffer: GpuFrameBuffer,
|
||||
edge_detect_shader: RenderPassContainer,
|
||||
@@ -77,11 +83,8 @@ impl HighlightRenderPass {
|
||||
pub fn new_raw(server: &dyn GraphicsServer, width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
framebuffer: Self::create_frame_buffer(server, width, height),
|
||||
edge_detect_shader: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("../resources/shaders/highlight.shader"),
|
||||
)
|
||||
.unwrap(),
|
||||
edge_detect_shader: RenderPassContainer::from_str(server, HIGHLIGHT_SHADER_SRC)
|
||||
.unwrap(),
|
||||
scene_handle: Default::default(),
|
||||
nodes_to_highlight: Default::default(),
|
||||
}
|
||||
@@ -107,6 +110,13 @@ impl SceneRenderPass for HighlightRenderPass {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
// Skip highlight rendering when nothing is selected.
|
||||
// Without this, the edge-detect shader reads stale FBO content on wgpu
|
||||
// because the deferred clear never executes when no draw calls happen.
|
||||
if self.nodes_to_highlight.is_empty() {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
// Draw selected nodes in the temporary frame buffer first.
|
||||
{
|
||||
let render_pass_name = ImmutableString::new("Forward");
|
||||
|
||||
+12
-1
@@ -273,10 +273,21 @@ macro_rules! load_image {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_opengl")]
|
||||
static GIZMO_SHADER: LazyLock<ShaderResource> = LazyLock::new(|| {
|
||||
ShaderResource::from_str(
|
||||
Uuid::new_v4(),
|
||||
include_str!("../resources/shaders/gizmo.shader"),
|
||||
include_str!("../resources/shaders/opengl/gizmo.shader"),
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
#[cfg(not(feature = "backend_opengl"))]
|
||||
static GIZMO_SHADER: LazyLock<ShaderResource> = LazyLock::new(|| {
|
||||
ShaderResource::from_str(
|
||||
Uuid::new_v4(),
|
||||
include_str!("../resources/shaders/wgpu/gizmo.shader"),
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap()
|
||||
|
||||
@@ -44,6 +44,12 @@ use crate::{
|
||||
use fyrox::core::uuid::Uuid;
|
||||
use std::{any::TypeId, cell::RefCell, rc::Rc};
|
||||
|
||||
#[cfg(feature = "backend_opengl")]
|
||||
const OVERLAY_SHADER_SRC: &str = include_str!("../resources/shaders/opengl/overlay.shader");
|
||||
|
||||
#[cfg(not(feature = "backend_opengl"))]
|
||||
const OVERLAY_SHADER_SRC: &str = include_str!("../resources/shaders/wgpu/overlay.shader");
|
||||
|
||||
pub struct OverlayRenderPass {
|
||||
quad: GpuGeometryBuffer,
|
||||
shader: RenderPassContainer,
|
||||
@@ -63,11 +69,7 @@ impl OverlayRenderPass {
|
||||
server,
|
||||
)
|
||||
.unwrap(),
|
||||
shader: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("../resources/shaders/overlay.shader"),
|
||||
)
|
||||
.unwrap(),
|
||||
shader: RenderPassContainer::from_str(server, OVERLAY_SHADER_SRC).unwrap(),
|
||||
sound_icon: TextureResource::load_from_memory(
|
||||
Uuid::new_v4(),
|
||||
ResourceKind::Embedded,
|
||||
|
||||
@@ -270,10 +270,21 @@ fn make_shape_gizmo(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_opengl")]
|
||||
static GIZMO_SHADER: LazyLock<ShaderResource> = LazyLock::new(|| {
|
||||
ShaderResource::from_str(
|
||||
Uuid::new_v4(),
|
||||
include_str!("../../../resources/shaders/sprite_gizmo.shader"),
|
||||
include_str!("../../../resources/shaders/opengl/sprite_gizmo.shader"),
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
#[cfg(not(feature = "backend_opengl"))]
|
||||
static GIZMO_SHADER: LazyLock<ShaderResource> = LazyLock::new(|| {
|
||||
ShaderResource::from_str(
|
||||
Uuid::new_v4(),
|
||||
include_str!("../../../resources/shaders/wgpu/sprite_gizmo.shader"),
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap()
|
||||
|
||||
+12
-1
@@ -152,10 +152,21 @@ pub struct GameScene {
|
||||
pub settings_receiver: Receiver<SettingsMessage>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_opengl")]
|
||||
static GRID_SHADER: LazyLock<ShaderResource> = LazyLock::new(|| {
|
||||
ShaderResource::from_str(
|
||||
Uuid::new_v4(),
|
||||
include_str!("../../resources/shaders/grid.shader"),
|
||||
include_str!("../../resources/shaders/opengl/grid.shader"),
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
#[cfg(not(feature = "backend_opengl"))]
|
||||
static GRID_SHADER: LazyLock<ShaderResource> = LazyLock::new(|| {
|
||||
ShaderResource::from_str(
|
||||
Uuid::new_v4(),
|
||||
include_str!("../../resources/shaders/wgpu/grid.shader"),
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap()
|
||||
|
||||
@@ -44,6 +44,8 @@ fn glsl_sampler_name(sampler: SamplerKind) -> &'static str {
|
||||
SamplerKind::USampler2D => "usampler2D",
|
||||
SamplerKind::USampler3D => "usampler3D",
|
||||
SamplerKind::USamplerCube => "usamplerCube",
|
||||
SamplerKind::DepthSampler2D => "sampler2D",
|
||||
SamplerKind::DepthSamplerCube => "samplerCube",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1494,7 +1494,8 @@ impl GraphicsServer for GlGraphicsServer {
|
||||
let gl = &self.gl;
|
||||
unsafe {
|
||||
ServerCapabilities {
|
||||
max_uniform_block_size: gl.get_parameter_i32(glow::MAX_UNIFORM_BLOCK_SIZE) as usize,
|
||||
max_uniform_buffer_binding_size: gl.get_parameter_i32(glow::MAX_UNIFORM_BLOCK_SIZE)
|
||||
as usize,
|
||||
uniform_buffer_offset_alignment: gl
|
||||
.get_parameter_i32(glow::UNIFORM_BUFFER_OFFSET_ALIGNMENT)
|
||||
as usize,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "fyrox-graphics-wgpu"
|
||||
version = "2.0.0-rc.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "Wgpu-based graphics server for Fyrox Game Engine"
|
||||
keywords = ["graphics", "gapi"]
|
||||
categories = ["graphics", "rendering::graphics-api"]
|
||||
include = ["/src/**/*", "/Cargo.toml", "/LICENSE", "/README.md"]
|
||||
homepage = "https://fyrox.rs"
|
||||
documentation = "https://docs.rs/fyrox-graphics-wgpu"
|
||||
repository = "https://github.com/FyroxEngine/Fyrox"
|
||||
rust-version = "1.87"
|
||||
|
||||
[dependencies]
|
||||
winit = { version = "0.30" }
|
||||
fyrox-graphics = { version = "2.0.0-rc.1", path = "../fyrox-graphics" }
|
||||
fyrox-core = { version = "2.0.0-rc.1", path = "../fyrox-core" }
|
||||
wgpu = { version = "30.0.0" }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
raw-window-handle = "0.6"
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use crate::server::WgpuGraphicsServer;
|
||||
use fyrox_graphics::{
|
||||
buffer::{BufferKind, BufferUsage, GpuBufferDescriptor, GpuBufferTrait},
|
||||
error::FrameworkError,
|
||||
};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Weak;
|
||||
|
||||
/// Maps a Fyrox [`BufferKind`] to the corresponding wgpu [`BufferUsages`] flags.
|
||||
///
|
||||
/// All buffers additionally get `COPY_DST` to allow data uploads via `queue.write_buffer`.
|
||||
/// `PixelWrite` buffers also get `COPY_SRC` to allow copying to readback buffers.
|
||||
fn buffer_usage_to_wgpu(kind: BufferKind) -> wgpu::BufferUsages {
|
||||
let mut flags = match kind {
|
||||
BufferKind::Vertex => wgpu::BufferUsages::VERTEX,
|
||||
BufferKind::Index => wgpu::BufferUsages::INDEX,
|
||||
BufferKind::Uniform => wgpu::BufferUsages::UNIFORM,
|
||||
BufferKind::PixelRead => wgpu::BufferUsages::MAP_READ,
|
||||
BufferKind::PixelWrite => wgpu::BufferUsages::MAP_WRITE,
|
||||
};
|
||||
flags |= wgpu::BufferUsages::COPY_DST;
|
||||
if kind == BufferKind::PixelWrite {
|
||||
flags |= wgpu::BufferUsages::COPY_SRC;
|
||||
}
|
||||
flags
|
||||
}
|
||||
|
||||
/// Wgpu implementation of [`GpuBufferTrait`].
|
||||
///
|
||||
/// Wraps a [`wgpu::Buffer`] and tracks its memory usage via `ServerMemoryUsage`.
|
||||
/// When `write_data` receives data larger than the current buffer capacity, the buffer
|
||||
/// is transparently reallocated to fit — matching the OpenGL backend behavior. Memory
|
||||
/// accounting is updated accordingly.
|
||||
pub struct WgpuBuffer {
|
||||
server: Weak<WgpuGraphicsServer>,
|
||||
buffer: RefCell<wgpu::Buffer>,
|
||||
size: Cell<usize>,
|
||||
kind: BufferKind,
|
||||
usage: BufferUsage,
|
||||
}
|
||||
|
||||
impl WgpuBuffer {
|
||||
/// Creates a new GPU buffer from the given descriptor.
|
||||
///
|
||||
/// The buffer is created with at least 1 byte of capacity (wgpu requires non-zero size).
|
||||
/// Memory usage is tracked on the server.
|
||||
pub fn new(
|
||||
server: &WgpuGraphicsServer,
|
||||
desc: GpuBufferDescriptor,
|
||||
) -> Result<Self, FrameworkError> {
|
||||
let wgpu_usage = buffer_usage_to_wgpu(desc.kind);
|
||||
let buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: if server.named_objects {
|
||||
Some(desc.name)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
size: desc.size.max(1) as u64,
|
||||
usage: wgpu_usage,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
server.memory_usage.borrow_mut().buffers += desc.size;
|
||||
Ok(Self {
|
||||
server: server.weak_ref(),
|
||||
buffer: RefCell::new(buffer),
|
||||
size: Cell::new(desc.size),
|
||||
kind: desc.kind,
|
||||
usage: desc.usage,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying [`wgpu::Buffer`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The returned reference must not be held across a call to `write_data`,
|
||||
/// which may replace the inner buffer. In practice this is always satisfied
|
||||
/// because `write_data` takes `&self` and completes before external access.
|
||||
pub unsafe fn wgpu_buffer_raw(&self) -> &wgpu::Buffer {
|
||||
// SAFETY: The caller guarantees no mutable borrow is active.
|
||||
unsafe { &*self.buffer.as_ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WgpuBuffer {
|
||||
fn drop(&mut self) {
|
||||
if let Some(server) = self.server.upgrade() {
|
||||
server.memory_usage.borrow_mut().buffers -= self.size.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuBufferTrait for WgpuBuffer {
|
||||
fn usage(&self) -> BufferUsage {
|
||||
self.usage
|
||||
}
|
||||
fn kind(&self) -> BufferKind {
|
||||
self.kind
|
||||
}
|
||||
fn size(&self) -> usize {
|
||||
self.size.get()
|
||||
}
|
||||
|
||||
fn write_data(&self, data: &[u8]) -> Result<(), FrameworkError> {
|
||||
if data.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(server) = self.server.upgrade() else {
|
||||
return Err(FrameworkError::GraphicsServerUnavailable);
|
||||
};
|
||||
if data.len() <= self.size.get() {
|
||||
server
|
||||
.state
|
||||
.queue
|
||||
.write_buffer(&self.buffer.borrow(), 0, data);
|
||||
} else {
|
||||
// Reallocate the buffer to fit the larger data, matching the GL backend
|
||||
// behavior. This prevents silent data truncation that caused rendering
|
||||
// artifacts (e.g. broken skeletal animation when bone count changes).
|
||||
let new_size = data.len();
|
||||
let wgpu_usage = buffer_usage_to_wgpu(self.kind);
|
||||
let new_buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
size: new_size as u64,
|
||||
usage: wgpu_usage,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
server.state.queue.write_buffer(&new_buffer, 0, data);
|
||||
|
||||
let mut mem = server.memory_usage.borrow_mut();
|
||||
mem.buffers -= self.size.get();
|
||||
mem.buffers += new_size;
|
||||
|
||||
*self.buffer.borrow_mut() = new_buffer;
|
||||
self.size.set(new_size);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_data(&self, data: &mut [u8]) -> Result<(), FrameworkError> {
|
||||
let Some(server) = self.server.upgrade() else {
|
||||
return Err(FrameworkError::GraphicsServerUnavailable);
|
||||
};
|
||||
|
||||
let buf = self.buffer.borrow();
|
||||
let buffer_slice = buf.slice(..data.len() as u64);
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
|
||||
tx.send(result).ok();
|
||||
});
|
||||
|
||||
server
|
||||
.state
|
||||
.device
|
||||
.poll(wgpu::PollType::Wait {
|
||||
submission_index: None,
|
||||
timeout: None,
|
||||
})
|
||||
.ok();
|
||||
|
||||
rx.recv()
|
||||
.map_err(|_| FrameworkError::Custom("Channel closed".into()))?
|
||||
.map_err(|e| FrameworkError::Custom(format!("Buffer map failed: {e}")))?;
|
||||
|
||||
let mapped = buffer_slice
|
||||
.get_mapped_range()
|
||||
.map_err(|e| FrameworkError::Custom(format!("Failed to get mapped range: {e}")))?;
|
||||
|
||||
data.copy_from_slice(&mapped);
|
||||
drop(mapped);
|
||||
buf.unmap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//! Shared texture format helpers and binding offset constants.
|
||||
//!
|
||||
//! This module centralizes format-related utilities used across the crate to avoid
|
||||
//! duplication and keep the binding convention in one place.
|
||||
|
||||
/// Offset added to a texture binding slot to obtain the corresponding sampler slot.
|
||||
///
|
||||
/// For a texture bound at slot `N`, its sampler is bound at `N + SAMPLER_BINDING_OFFSET`.
|
||||
pub const SAMPLER_BINDING_OFFSET: usize = 100;
|
||||
|
||||
/// Offset added to a resource binding slot to obtain the uniform buffer slot.
|
||||
///
|
||||
/// For a property group at binding `N`, its uniform buffer is bound at
|
||||
/// `N + UNIFORM_BINDING_OFFSET`.
|
||||
pub const UNIFORM_BINDING_OFFSET: usize = 200;
|
||||
|
||||
/// Returns `true` if the given texture format supports filtering samplers.
|
||||
///
|
||||
/// Non-filterable formats (32-bit float, uint, sint) require
|
||||
/// [`wgpu::SamplerBindingType::NonFiltering`] when creating bind group layouts.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use fyrox_graphics_wgpu::format_helpers::is_filterable_format;
|
||||
///
|
||||
/// assert!(is_filterable_format(wgpu::TextureFormat::Rgba8Unorm));
|
||||
/// assert!(!is_filterable_format(wgpu::TextureFormat::R32Float));
|
||||
/// ```
|
||||
pub fn is_filterable_format(fmt: wgpu::TextureFormat) -> bool {
|
||||
!matches!(
|
||||
fmt,
|
||||
wgpu::TextureFormat::R32Float
|
||||
| wgpu::TextureFormat::Rg32Float
|
||||
| wgpu::TextureFormat::Rgba32Float
|
||||
| wgpu::TextureFormat::R8Uint
|
||||
| wgpu::TextureFormat::R16Uint
|
||||
| wgpu::TextureFormat::R32Uint
|
||||
| wgpu::TextureFormat::R8Sint
|
||||
| wgpu::TextureFormat::R16Sint
|
||||
| wgpu::TextureFormat::R32Sint
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if the given texture format is an integer format.
|
||||
///
|
||||
/// Hardware blending (e.g., alpha blending) relies on floating-point arithmetic.
|
||||
/// Integer formats (such as `R8Uint`, `R16Sint`, etc.) cannot be blended by the GPU.
|
||||
/// When configuring a [`wgpu::ColorTargetState`] for these formats, the `blend`
|
||||
/// field MUST be explicitly set to `None` to prevent `wgpu` validation errors.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use fyrox_graphics_wgpu::format_helpers::is_integer_format;
|
||||
///
|
||||
/// assert!(is_integer_format(wgpu::TextureFormat::R8Uint));
|
||||
/// assert!(!is_integer_format(wgpu::TextureFormat::Rgba8Unorm));
|
||||
/// ```
|
||||
pub fn is_integer_format(fmt: wgpu::TextureFormat) -> bool {
|
||||
use wgpu::TextureFormat as F;
|
||||
matches!(
|
||||
fmt,
|
||||
F::R8Uint
|
||||
| F::R8Sint
|
||||
| F::R16Uint
|
||||
| F::R16Sint
|
||||
| F::R32Uint
|
||||
| F::R32Sint
|
||||
| F::Rg8Uint
|
||||
| F::Rg8Sint
|
||||
| F::Rg16Uint
|
||||
| F::Rg16Sint
|
||||
| F::Rg32Uint
|
||||
| F::Rg32Sint
|
||||
| F::Rgba8Uint
|
||||
| F::Rgba8Sint
|
||||
| F::Rgba16Uint
|
||||
| F::Rgba16Sint
|
||||
| F::Rgba32Uint
|
||||
| F::Rgba32Sint
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the appropriate [`wgpu::TextureSampleType`] for the given texture format.
|
||||
///
|
||||
/// Used when creating bind group layouts from actual texture formats rather than
|
||||
/// from [`SamplerKind`](fyrox_graphics::gpu_program::SamplerKind) inference.
|
||||
/// Handles depth, uint, sint, and non-filterable float formats correctly.
|
||||
pub fn sample_type_for_format(fmt: wgpu::TextureFormat) -> wgpu::TextureSampleType {
|
||||
use wgpu::TextureFormat as F;
|
||||
match fmt {
|
||||
F::Depth16Unorm
|
||||
| F::Depth24Plus
|
||||
| F::Depth24PlusStencil8
|
||||
| F::Depth32Float
|
||||
| F::Depth32FloatStencil8 => wgpu::TextureSampleType::Depth,
|
||||
F::R8Uint
|
||||
| F::R16Uint
|
||||
| F::R32Uint
|
||||
| F::Rg8Uint
|
||||
| F::Rg16Uint
|
||||
| F::Rg32Uint
|
||||
| F::Rgba8Uint
|
||||
| F::Rgba16Uint
|
||||
| F::Rgba32Uint => wgpu::TextureSampleType::Uint,
|
||||
F::R8Sint
|
||||
| F::R16Sint
|
||||
| F::R32Sint
|
||||
| F::Rg8Sint
|
||||
| F::Rg16Sint
|
||||
| F::Rg32Sint
|
||||
| F::Rgba8Sint
|
||||
| F::Rgba16Sint
|
||||
| F::Rgba32Sint => wgpu::TextureSampleType::Sint,
|
||||
F::R32Float | F::Rg32Float | F::Rgba32Float => {
|
||||
wgpu::TextureSampleType::Float { filterable: false }
|
||||
}
|
||||
_ => wgpu::TextureSampleType::Float { filterable: true },
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,241 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use crate::server::WgpuGraphicsServer;
|
||||
use fyrox_graphics::{
|
||||
core::{array_as_u8_slice, math::TriangleDefinition},
|
||||
error::FrameworkError,
|
||||
geometry_buffer::{
|
||||
AttributeKind, ElementsDescriptor, GpuGeometryBufferDescriptor, GpuGeometryBufferTrait,
|
||||
},
|
||||
ElementKind,
|
||||
};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Weak;
|
||||
|
||||
/// Maps a Fyrox [`AttributeKind`] + component count + normalization flag to a
|
||||
/// wgpu [`VertexFormat`].
|
||||
fn attribute_format(kind: AttributeKind, cc: usize, normalized: bool) -> wgpu::VertexFormat {
|
||||
match (kind, cc, normalized) {
|
||||
(AttributeKind::Float, 1, _) => wgpu::VertexFormat::Float32,
|
||||
(AttributeKind::Float, 2, _) => wgpu::VertexFormat::Float32x2,
|
||||
(AttributeKind::Float, 3, _) => wgpu::VertexFormat::Float32x3,
|
||||
(AttributeKind::Float, 4, _) => wgpu::VertexFormat::Float32x4,
|
||||
(AttributeKind::UnsignedByte, 4, true) => wgpu::VertexFormat::Unorm8x4,
|
||||
(AttributeKind::UnsignedByte, 4, false) => wgpu::VertexFormat::Uint8x4,
|
||||
(AttributeKind::UnsignedShort, 2, true) => wgpu::VertexFormat::Unorm16x2,
|
||||
(AttributeKind::UnsignedShort, 2, false) => wgpu::VertexFormat::Uint16x2,
|
||||
(AttributeKind::UnsignedShort, 4, true) => wgpu::VertexFormat::Unorm16x4,
|
||||
(AttributeKind::UnsignedShort, 4, false) => wgpu::VertexFormat::Uint16x4,
|
||||
(AttributeKind::UnsignedInt, 1, _) => wgpu::VertexFormat::Uint32,
|
||||
(AttributeKind::UnsignedInt, 2, _) => wgpu::VertexFormat::Uint32x2,
|
||||
(AttributeKind::UnsignedInt, 3, _) => wgpu::VertexFormat::Uint32x3,
|
||||
(AttributeKind::UnsignedInt, 4, _) => wgpu::VertexFormat::Uint32x4,
|
||||
_ => wgpu::VertexFormat::Float32x4,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wgpu implementation of [`GpuGeometryBufferTrait`].
|
||||
///
|
||||
/// Holds vertex buffers, their layouts, and an index (element) buffer for indexed
|
||||
/// drawing. Supports triangles, lines, and points as element types.
|
||||
///
|
||||
/// Vertex attribute layouts are leaked once at construction to satisfy wgpu's
|
||||
/// `'static` lifetime requirement — see the comment in [`new`](Self::new) for details.
|
||||
pub struct WgpuGeometryBuffer {
|
||||
_server: Weak<WgpuGraphicsServer>,
|
||||
vertex_buffers: RefCell<Vec<wgpu::Buffer>>,
|
||||
vertex_buffer_layouts: Vec<wgpu::VertexBufferLayout<'static>>,
|
||||
element_buffer: RefCell<wgpu::Buffer>,
|
||||
element_count: Cell<usize>,
|
||||
element_kind: ElementKind,
|
||||
}
|
||||
|
||||
impl WgpuGeometryBuffer {
|
||||
/// Creates a new geometry buffer from the given descriptor.
|
||||
///
|
||||
/// Creates vertex buffers with their attribute layouts and an index buffer.
|
||||
/// Attribute layouts are leaked once to satisfy wgpu's `'static` lifetime
|
||||
/// requirement for `VertexBufferLayout::attributes`.
|
||||
pub fn new(
|
||||
server: &WgpuGraphicsServer,
|
||||
desc: GpuGeometryBufferDescriptor,
|
||||
) -> Result<Self, FrameworkError> {
|
||||
let mut vertex_buffers = Vec::new();
|
||||
let mut vertex_buffer_layouts = Vec::new();
|
||||
|
||||
for (i, buf) in desc.buffers.iter().enumerate() {
|
||||
let data_size = buf.data.bytes.map(|b| b.len()).unwrap_or(0);
|
||||
let label_str = format!("{}VB{i}", desc.name);
|
||||
let buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: if server.named_objects {
|
||||
Some(&label_str)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
size: data_size.max(1) as u64,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
if let Some(data) = buf.data.bytes {
|
||||
if !data.is_empty() {
|
||||
server.state.queue.write_buffer(&buffer, 0, data);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attributes = Vec::new();
|
||||
let mut offset = 0u64;
|
||||
for attr in buf.attributes {
|
||||
attributes.push(wgpu::VertexAttribute {
|
||||
format: attribute_format(attr.kind, attr.component_count, attr.normalized),
|
||||
offset,
|
||||
shader_location: attr.location,
|
||||
});
|
||||
offset += (attr.kind.size() * attr.component_count) as u64;
|
||||
}
|
||||
|
||||
let step_mode = if buf.attributes.iter().any(|a| a.divisor > 0) {
|
||||
wgpu::VertexStepMode::Instance
|
||||
} else {
|
||||
wgpu::VertexStepMode::Vertex
|
||||
};
|
||||
// NOTE: Attributes are leaked to satisfy wgpu's `'static` lifetime requirement
|
||||
// for `VertexBufferLayout::attributes`. Each geometry buffer leaks one small
|
||||
// `Vec<VertexAttribute>` per vertex buffer (typically < 100 bytes). This is
|
||||
// acceptable because geometry buffers are long-lived (tied to mesh lifetime)
|
||||
// and relatively few in number.
|
||||
vertex_buffer_layouts.push(wgpu::VertexBufferLayout {
|
||||
array_stride: buf.data.element_size as u64,
|
||||
step_mode,
|
||||
attributes: attributes.leak(),
|
||||
});
|
||||
vertex_buffers.push(buffer);
|
||||
}
|
||||
|
||||
let (element_count, element_data) = match desc.elements {
|
||||
ElementsDescriptor::Triangles(t) => (t.len(), array_as_u8_slice(t)),
|
||||
ElementsDescriptor::Lines(l) => (l.len(), array_as_u8_slice(l)),
|
||||
ElementsDescriptor::Points(p) => (p.len(), array_as_u8_slice(p)),
|
||||
};
|
||||
|
||||
let ib_label = format!("{}IB", desc.name);
|
||||
let element_buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: if server.named_objects {
|
||||
Some(&ib_label)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
size: element_data.len().max(1) as u64,
|
||||
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
if !element_data.is_empty() {
|
||||
server
|
||||
.state
|
||||
.queue
|
||||
.write_buffer(&element_buffer, 0, element_data);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
_server: server.weak_ref(),
|
||||
vertex_buffers: RefCell::new(vertex_buffers),
|
||||
vertex_buffer_layouts,
|
||||
element_buffer: RefCell::new(element_buffer),
|
||||
element_count: Cell::new(element_count),
|
||||
element_kind: desc.elements.element_kind(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the number of elements (triangles, lines, or points) in the index buffer.
|
||||
pub fn element_count(&self) -> usize {
|
||||
self.element_count.get()
|
||||
}
|
||||
/// Returns the type of elements stored in the index buffer.
|
||||
pub fn element_kind(&self) -> ElementKind {
|
||||
self.element_kind
|
||||
}
|
||||
/// Returns a borrow of the vertex buffer list.
|
||||
pub fn vertex_buffers(&self) -> std::cell::Ref<'_, Vec<wgpu::Buffer>> {
|
||||
self.vertex_buffers.borrow()
|
||||
}
|
||||
/// Returns the vertex buffer layout descriptors for all vertex buffers.
|
||||
pub fn vertex_buffer_layouts(&self) -> &[wgpu::VertexBufferLayout<'static>] {
|
||||
&self.vertex_buffer_layouts
|
||||
}
|
||||
/// Returns a borrow of the index (element) buffer.
|
||||
pub fn element_buffer(&self) -> std::cell::Ref<'_, wgpu::Buffer> {
|
||||
self.element_buffer.borrow()
|
||||
}
|
||||
|
||||
/// Writes index data to the element buffer, recreating it if the new data is larger
|
||||
/// than the current buffer capacity.
|
||||
fn write_element_data(&self, count: usize, data: &[u8]) {
|
||||
if let Some(server) = self._server.upgrade() {
|
||||
self.element_count.set(count);
|
||||
let mut eb = self.element_buffer.borrow_mut();
|
||||
if (data.len() as u64) <= eb.size() {
|
||||
server.state.queue.write_buffer(&eb, 0, data);
|
||||
} else {
|
||||
let new_buf = server.state.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
size: data.len() as u64,
|
||||
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
server.state.queue.write_buffer(&new_buf, 0, data);
|
||||
*eb = new_buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuGeometryBufferTrait for WgpuGeometryBuffer {
|
||||
fn set_buffer_data(&self, buffer_idx: usize, data: &[u8]) {
|
||||
if let Some(server) = self._server.upgrade() {
|
||||
let mut bufs = self.vertex_buffers.borrow_mut();
|
||||
if let Some(buf) = bufs.get(buffer_idx) {
|
||||
if (data.len() as u64) <= buf.size() {
|
||||
server.state.queue.write_buffer(buf, 0, data);
|
||||
} else {
|
||||
let new_buf = server.state.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
size: data.len() as u64,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
server.state.queue.write_buffer(&new_buf, 0, data);
|
||||
bufs[buffer_idx] = new_buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn element_count(&self) -> usize {
|
||||
self.element_count.get()
|
||||
}
|
||||
fn set_triangles(&self, triangles: &[TriangleDefinition]) {
|
||||
self.write_element_data(triangles.len(), array_as_u8_slice(triangles));
|
||||
}
|
||||
fn set_lines(&self, lines: &[[u32; 2]]) {
|
||||
self.write_element_data(lines.len(), array_as_u8_slice(lines));
|
||||
}
|
||||
fn set_points(&self, points: &[u32]) {
|
||||
self.write_element_data(points.len(), array_as_u8_slice(points));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//! Wgpu-based graphics server implementation for the Fyrox game engine.
|
||||
//!
|
||||
//! This crate provides a concrete [`GraphicsServer`](fyrox_graphics::server::GraphicsServer)
|
||||
//! implementation backed by the [wgpu](https://wgpu.rs/) graphics API. It is the primary
|
||||
//! rendering backend for Fyrox and supports desktop (Vulkan/Metal/DX12) and web (WebGL2/WebGPU)
|
||||
//! targets.
|
||||
//!
|
||||
//! # Architecture
|
||||
//!
|
||||
//! The entry point is [`server::WgpuGraphicsServer`], which creates and owns the wgpu
|
||||
//! device, queue, and surface. All GPU resources (textures, buffers, shaders, etc.) are
|
||||
//! created through the server and implement the corresponding trait from [`fyrox_graphics`].
|
||||
//!
|
||||
//! ```text
|
||||
//! WgpuGraphicsServer
|
||||
//! ├── WgpuTexture (texture.rs) — 1D/2D/3D/Cube textures
|
||||
//! ├── WgpuBuffer (buffer.rs) — uniform/vertex/index buffers
|
||||
//! ├── WgpuSampler (sampler.rs) — texture sampling state
|
||||
//! ├── WgpuGeometryBuffer (geometry_buffer.rs) — mesh vertex + index data
|
||||
//! ├── WgpuFrameBuffer (framebuffer.rs) — render targets + draw calls
|
||||
//! ├── WgpuProgram (program.rs) — compiled shader programs
|
||||
//! ├── WgpuQuery (query.rs) — GPU queries (stub)
|
||||
//! └── WgpuAsyncReadBuffer(read_buffer.rs) — async pixel readback
|
||||
//! ```
|
||||
//!
|
||||
//! # Binding Layout
|
||||
//!
|
||||
//! All shader resources use a fixed binding scheme within bind group 0:
|
||||
//!
|
||||
//! | Resource kind | Binding slot |
|
||||
//! |---------------------|------------------------|
|
||||
//! | Texture view | `N` |
|
||||
//! | Sampler | `N + 100` |
|
||||
//! | Uniform buffer | `N + 200` |
|
||||
//!
|
||||
//! Where `N` is the resource's declared binding index from `ShaderResourceDefinition`.
|
||||
//!
|
||||
//! # Pipeline Caching
|
||||
//!
|
||||
//! Render pipelines are immutable and expensive to create. The backend caches them
|
||||
//! by a hash of the full render state (program, formats, blend, depth, stencil, cull)
|
||||
//! in a per-server [`HashMap`](std::collections::HashMap).
|
||||
//!
|
||||
//! # Shared Shader Library
|
||||
//!
|
||||
//! Every compiled shader includes `shaders/shared.wgsl`, which provides PBR lighting,
|
||||
//! shadow mapping, color space conversion, and other common utilities.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
/// Generic GPU buffer implementation (uniform, vertex, index, pixel read/write).
|
||||
pub mod buffer;
|
||||
/// Texture format helpers and binding offset constants.
|
||||
pub mod format_helpers;
|
||||
/// Render target (framebuffer) implementation with draw call logic and pipeline caching.
|
||||
pub mod framebuffer;
|
||||
/// Geometry buffer implementation (vertex + index buffers for mesh rendering).
|
||||
pub mod geometry_buffer;
|
||||
/// Shader compilation and program management.
|
||||
pub mod program;
|
||||
/// GPU query stub implementation.
|
||||
pub mod query;
|
||||
/// Async pixel readback buffer implementation.
|
||||
pub mod read_buffer;
|
||||
/// Texture sampler implementation.
|
||||
pub mod sampler;
|
||||
/// The main graphics server — entry point for all GPU resource creation.
|
||||
pub mod server;
|
||||
/// Texture creation, upload, and format mapping.
|
||||
pub mod texture;
|
||||
@@ -0,0 +1,601 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use crate::format_helpers::{
|
||||
sample_type_for_format, SAMPLER_BINDING_OFFSET, UNIFORM_BINDING_OFFSET,
|
||||
};
|
||||
use crate::server::WgpuGraphicsServer;
|
||||
use fyrox_graphics::{
|
||||
core::log::{Log, MessageKind},
|
||||
error::FrameworkError,
|
||||
gpu_program::{
|
||||
GpuProgramTrait, GpuShaderTrait, SamplerKind, ShaderKind, ShaderPropertyKind,
|
||||
ShaderResourceDefinition, ShaderResourceKind,
|
||||
},
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Weak;
|
||||
|
||||
/// Returns the WGSL texture type for a given sampler kind.
|
||||
fn wgsl_texture_type(kind: SamplerKind) -> &'static str {
|
||||
match kind {
|
||||
SamplerKind::Sampler1D => "texture_1d<f32>",
|
||||
SamplerKind::Sampler2D => "texture_2d<f32>",
|
||||
SamplerKind::Sampler3D => "texture_3d<f32>",
|
||||
SamplerKind::SamplerCube => "texture_cube<f32>",
|
||||
SamplerKind::USampler1D => "texture_1d<u32>",
|
||||
SamplerKind::USampler2D => "texture_2d<u32>",
|
||||
SamplerKind::USampler3D => "texture_3d<u32>",
|
||||
SamplerKind::USamplerCube => "texture_cube<u32>",
|
||||
SamplerKind::DepthSampler2D => "texture_depth_2d",
|
||||
SamplerKind::DepthSamplerCube => "texture_depth_cube",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the WGSL type string for a shader property kind.
|
||||
fn wgsl_property_type(kind: &ShaderPropertyKind) -> String {
|
||||
match kind {
|
||||
ShaderPropertyKind::Float { .. } => "f32".into(),
|
||||
ShaderPropertyKind::FloatArray { max_len, .. } => format!("array<vec4f, {max_len}>"),
|
||||
ShaderPropertyKind::Int { .. } => "i32".into(),
|
||||
ShaderPropertyKind::IntArray { max_len, .. } => format!("array<vec4i, {max_len}>"),
|
||||
ShaderPropertyKind::UInt { .. } => "u32".into(),
|
||||
ShaderPropertyKind::UIntArray { max_len, .. } => format!("array<vec4u, {max_len}>"),
|
||||
ShaderPropertyKind::Bool { .. } => "u32".into(),
|
||||
ShaderPropertyKind::Vector2 { .. } => "vec2f".into(),
|
||||
ShaderPropertyKind::Vector2Array { max_len, .. } => format!("array<vec4f, {max_len}>"),
|
||||
ShaderPropertyKind::Vector3 { .. } => "vec3f".into(),
|
||||
ShaderPropertyKind::Vector3Array { max_len, .. } => format!("array<vec3f, {max_len}>"),
|
||||
ShaderPropertyKind::Vector4 { .. } => "vec4f".into(),
|
||||
ShaderPropertyKind::Vector4Array { max_len, .. } => format!("array<vec4f, {max_len}>"),
|
||||
ShaderPropertyKind::Matrix2 { .. } => "mat2x2f".into(),
|
||||
ShaderPropertyKind::Matrix2Array { max_len, .. } => format!("array<mat2x2f, {max_len}>"),
|
||||
ShaderPropertyKind::Matrix3 { .. } => "mat3x3f".into(),
|
||||
ShaderPropertyKind::Matrix3Array { max_len, .. } => format!("array<mat3x3f, {max_len}>"),
|
||||
ShaderPropertyKind::Matrix4 { .. } => "mat4x4f".into(),
|
||||
ShaderPropertyKind::Matrix4Array { max_len, .. } => format!("array<mat4x4f, {max_len}>"),
|
||||
ShaderPropertyKind::Color { .. } => "vec4f".into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates WGSL `@group(0) @binding(N)` declarations for textures, samplers,
|
||||
/// and uniform buffers from the shader resource definitions.
|
||||
fn generate_wgsl_declarations(resources: &[ShaderResourceDefinition]) -> String {
|
||||
let mut decls = String::new();
|
||||
|
||||
for res in resources {
|
||||
match res.kind {
|
||||
ShaderResourceKind::Texture { kind, .. } => {
|
||||
let tex_type = wgsl_texture_type(kind);
|
||||
decls += &format!(
|
||||
"@group(0) @binding({}) var {}_tex: {};\n",
|
||||
res.binding, res.name, tex_type
|
||||
);
|
||||
decls += &format!(
|
||||
"@group(0) @binding({}) var {}_samp: sampler;\n",
|
||||
res.binding + SAMPLER_BINDING_OFFSET,
|
||||
res.name
|
||||
);
|
||||
}
|
||||
ShaderResourceKind::PropertyGroup(ref fields) => {
|
||||
if fields.is_empty() {
|
||||
continue;
|
||||
}
|
||||
decls += &format!("struct T{} {{\n", res.name);
|
||||
for f in fields {
|
||||
let n = &f.name;
|
||||
let ty = wgsl_property_type(&f.kind);
|
||||
decls += &format!(" {n}: {ty},\n");
|
||||
}
|
||||
decls += "}\n";
|
||||
decls += &format!(
|
||||
"@group(0) @binding({}) var<uniform> {}: T{};\n",
|
||||
res.binding + UNIFORM_BINDING_OFFSET,
|
||||
res.name,
|
||||
res.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decls
|
||||
}
|
||||
|
||||
/// Compiles WGSL source into a wgpu shader module.
|
||||
fn compile_wgsl(
|
||||
device: &wgpu::Device,
|
||||
name: &str,
|
||||
wgsl: &str,
|
||||
) -> Result<wgpu::ShaderModule, FrameworkError> {
|
||||
let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some(name),
|
||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(wgsl)),
|
||||
});
|
||||
Log::writeln(
|
||||
MessageKind::Information,
|
||||
format!("Shader {name} compiled successfully!"),
|
||||
);
|
||||
Ok(shader_module)
|
||||
}
|
||||
|
||||
/// Creates bind group layout using actual texture formats when available.
|
||||
/// `texture_formats` provides the actual wgpu format for specific bindings.
|
||||
fn create_bind_group_layout_with_formats(
|
||||
device: &wgpu::Device,
|
||||
resources: &[ShaderResourceDefinition],
|
||||
texture_formats: &[(usize, wgpu::TextureFormat)],
|
||||
) -> wgpu::BindGroupLayout {
|
||||
let fmt_map: std::collections::HashMap<usize, wgpu::TextureFormat> =
|
||||
texture_formats.iter().copied().collect();
|
||||
let mut entries = Vec::new();
|
||||
for res in resources {
|
||||
match res.kind {
|
||||
ShaderResourceKind::Texture { kind, .. } => {
|
||||
let vd = match kind {
|
||||
SamplerKind::Sampler1D | SamplerKind::USampler1D => {
|
||||
wgpu::TextureViewDimension::D1
|
||||
}
|
||||
SamplerKind::Sampler2D
|
||||
| SamplerKind::USampler2D
|
||||
| SamplerKind::DepthSampler2D => wgpu::TextureViewDimension::D2,
|
||||
SamplerKind::Sampler3D | SamplerKind::USampler3D => {
|
||||
wgpu::TextureViewDimension::D3
|
||||
}
|
||||
SamplerKind::SamplerCube
|
||||
| SamplerKind::USamplerCube
|
||||
| SamplerKind::DepthSamplerCube => wgpu::TextureViewDimension::Cube,
|
||||
};
|
||||
// Use actual texture format if available, otherwise fall back to kind-based inference
|
||||
let st = if let Some(&fmt) = fmt_map.get(&res.binding) {
|
||||
sample_type_for_format(fmt)
|
||||
} else {
|
||||
match kind {
|
||||
SamplerKind::DepthSampler2D | SamplerKind::DepthSamplerCube => {
|
||||
wgpu::TextureSampleType::Depth
|
||||
}
|
||||
SamplerKind::USampler1D
|
||||
| SamplerKind::USampler2D
|
||||
| SamplerKind::USampler3D
|
||||
| SamplerKind::USamplerCube => wgpu::TextureSampleType::Uint,
|
||||
_ => wgpu::TextureSampleType::Float { filterable: true },
|
||||
}
|
||||
};
|
||||
entries.push(wgpu::BindGroupLayoutEntry {
|
||||
binding: res.binding as u32,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: st,
|
||||
view_dimension: vd,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
});
|
||||
let sampler_binding = if matches!(
|
||||
st,
|
||||
wgpu::TextureSampleType::Float { filterable: false }
|
||||
| wgpu::TextureSampleType::Uint
|
||||
| wgpu::TextureSampleType::Sint
|
||||
) {
|
||||
wgpu::SamplerBindingType::NonFiltering
|
||||
} else {
|
||||
wgpu::SamplerBindingType::Filtering
|
||||
};
|
||||
entries.push(wgpu::BindGroupLayoutEntry {
|
||||
binding: (res.binding + SAMPLER_BINDING_OFFSET) as u32,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(sampler_binding),
|
||||
count: None,
|
||||
});
|
||||
}
|
||||
ShaderResourceKind::PropertyGroup { .. } => {
|
||||
entries.push(wgpu::BindGroupLayoutEntry {
|
||||
binding: (res.binding + UNIFORM_BINDING_OFFSET) as u32,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("ShaderBindGroupLayout"),
|
||||
entries: &entries,
|
||||
})
|
||||
}
|
||||
|
||||
/// Wgpu implementation of [`GpuShaderTrait`].
|
||||
///
|
||||
/// Wraps a compiled [`wgpu::ShaderModule`]. Shaders are compiled from WGSL source
|
||||
/// with automatically generated resource binding declarations prepended.
|
||||
pub struct WgpuShader {
|
||||
_server: Weak<WgpuGraphicsServer>,
|
||||
module: wgpu::ShaderModule,
|
||||
_kind: ShaderKind,
|
||||
}
|
||||
|
||||
impl GpuShaderTrait for WgpuShader {}
|
||||
|
||||
impl WgpuShader {
|
||||
/// Compiles a WGSL shader from source with resource binding declarations.
|
||||
///
|
||||
/// The compilation pipeline:
|
||||
/// 1. Validates resource definitions for duplicate bindings/names
|
||||
/// 2. Generates `@group(0) @binding(N)` WGSL declarations via `generate_wgsl_declarations`
|
||||
/// 3. Prepends declarations + [`shared.wgsl`](shaders/shared.wgsl) to the user source
|
||||
/// 4. Compiles the combined WGSL as a [`wgpu::ShaderModule`]
|
||||
pub fn new(
|
||||
server: &WgpuGraphicsServer,
|
||||
name: String,
|
||||
kind: ShaderKind,
|
||||
source: String,
|
||||
resources: &[ShaderResourceDefinition],
|
||||
_line_offset: isize,
|
||||
) -> Result<Self, FrameworkError> {
|
||||
for r in resources {
|
||||
for o in resources {
|
||||
if std::ptr::eq(r, o) {
|
||||
continue;
|
||||
}
|
||||
if std::mem::discriminant(&r.kind) == std::mem::discriminant(&o.kind) {
|
||||
if r.binding == o.binding {
|
||||
return Err(FrameworkError::Custom(format!(
|
||||
"Resource {} and {} same binding {}",
|
||||
r.name, o.name, r.binding
|
||||
)));
|
||||
}
|
||||
if r.name == o.name {
|
||||
return Err(FrameworkError::Custom(format!(
|
||||
"Duplicate resource name {}",
|
||||
r.name
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let declarations = generate_wgsl_declarations(resources);
|
||||
let shared = include_str!("shaders/shared.wgsl");
|
||||
|
||||
let mut wgsl = String::new();
|
||||
wgsl += &declarations;
|
||||
wgsl += shared;
|
||||
wgsl += "\n";
|
||||
wgsl += &source;
|
||||
|
||||
let module = compile_wgsl(&server.state.device, &name, &wgsl)?;
|
||||
Ok(Self {
|
||||
_server: server.weak_ref(),
|
||||
module,
|
||||
_kind: kind,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a reference to the compiled [`wgpu::ShaderModule`].
|
||||
pub fn wgpu_module(&self) -> &wgpu::ShaderModule {
|
||||
&self.module
|
||||
}
|
||||
}
|
||||
|
||||
/// Wgpu implementation of [`GpuProgramTrait`].
|
||||
///
|
||||
/// A shader program consisting of a vertex and fragment [`wgpu::ShaderModule`],
|
||||
/// along with resource definitions and lazily-cached bind group / pipeline layouts.
|
||||
///
|
||||
/// The layout cache is keyed on the actual texture formats passed to
|
||||
/// [`get_or_create_layouts`](Self::get_or_create_layouts), ensuring correct
|
||||
/// sample type inference for each unique set of bound textures.
|
||||
pub struct WgpuProgram {
|
||||
server: Weak<WgpuGraphicsServer>,
|
||||
name: String,
|
||||
vertex_module: wgpu::ShaderModule,
|
||||
fragment_module: wgpu::ShaderModule,
|
||||
resources: Vec<ShaderResourceDefinition>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
cached_layouts: RefCell<
|
||||
HashMap<
|
||||
Vec<(usize, wgpu::TextureSampleType)>,
|
||||
(wgpu::BindGroupLayout, wgpu::PipelineLayout),
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl GpuProgramTrait for WgpuProgram {}
|
||||
|
||||
impl WgpuProgram {
|
||||
/// Creates a program by compiling vertex and fragment shaders from source.
|
||||
///
|
||||
/// Both shaders share the same resource definitions. The vertex shader is
|
||||
/// named `{name}_VS` and the fragment shader `{name}_FS`.
|
||||
pub fn from_source(
|
||||
server: &WgpuGraphicsServer,
|
||||
name: &str,
|
||||
vs: String,
|
||||
vs_off: isize,
|
||||
fs: String,
|
||||
fs_off: isize,
|
||||
resources: &[ShaderResourceDefinition],
|
||||
) -> Result<Self, FrameworkError> {
|
||||
let vert = WgpuShader::new(
|
||||
server,
|
||||
format!("{name}_VS"),
|
||||
ShaderKind::Vertex,
|
||||
vs,
|
||||
resources,
|
||||
vs_off,
|
||||
)?;
|
||||
let frag = WgpuShader::new(
|
||||
server,
|
||||
format!("{name}_FS"),
|
||||
ShaderKind::Fragment,
|
||||
fs,
|
||||
resources,
|
||||
fs_off,
|
||||
)?;
|
||||
Self::from_modules(server, name, &vert, &frag, resources)
|
||||
}
|
||||
|
||||
/// Creates a program from pre-compiled shaders.
|
||||
///
|
||||
/// The shaders must be [`WgpuShader`] instances (downcast from trait objects).
|
||||
pub fn from_shaders(
|
||||
server: &WgpuGraphicsServer,
|
||||
name: &str,
|
||||
vs: &fyrox_graphics::gpu_program::GpuShader,
|
||||
fs: &fyrox_graphics::gpu_program::GpuShader,
|
||||
resources: &[ShaderResourceDefinition],
|
||||
) -> Result<Self, FrameworkError> {
|
||||
let vert = vs
|
||||
.as_any()
|
||||
.downcast_ref::<WgpuShader>()
|
||||
.ok_or_else(|| FrameworkError::Custom("Expected WgpuShader".into()))?;
|
||||
let frag = fs
|
||||
.as_any()
|
||||
.downcast_ref::<WgpuShader>()
|
||||
.ok_or_else(|| FrameworkError::Custom("Expected WgpuShader".into()))?;
|
||||
Self::from_modules(server, name, vert, frag, resources)
|
||||
}
|
||||
|
||||
fn from_modules(
|
||||
server: &WgpuGraphicsServer,
|
||||
name: &str,
|
||||
vert: &WgpuShader,
|
||||
frag: &WgpuShader,
|
||||
resources: &[ShaderResourceDefinition],
|
||||
) -> Result<Self, FrameworkError> {
|
||||
Ok(Self {
|
||||
server: server.weak_ref(),
|
||||
name: name.to_owned(),
|
||||
vertex_module: vert.module.clone(),
|
||||
fragment_module: frag.module.clone(),
|
||||
resources: resources.to_vec(),
|
||||
cached_layouts: RefCell::new(HashMap::new()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Lazily create bind group layout + pipeline layout based on actual texture formats.
|
||||
/// `texture_formats` maps resource binding -> actual wgpu texture format for textures.
|
||||
pub fn get_or_create_layouts(
|
||||
&self,
|
||||
texture_formats: &[(usize, wgpu::TextureFormat)],
|
||||
) -> (wgpu::BindGroupLayout, wgpu::PipelineLayout) {
|
||||
let sample_types: Vec<_> = texture_formats
|
||||
.iter()
|
||||
.map(|(loc, fmt)| (*loc, sample_type_for_format(*fmt)))
|
||||
.collect();
|
||||
|
||||
let mut cache = self.cached_layouts.borrow_mut();
|
||||
|
||||
if let Some((bgl, pl)) = cache.get(&sample_types) {
|
||||
return (bgl.clone(), pl.clone());
|
||||
}
|
||||
|
||||
let server = self
|
||||
.server
|
||||
.upgrade()
|
||||
.expect("WgpuGraphicsServer dropped before WgpuProgram");
|
||||
|
||||
let bgl = create_bind_group_layout_with_formats(
|
||||
&server.state.device,
|
||||
&self.resources,
|
||||
texture_formats,
|
||||
);
|
||||
|
||||
let pl = server
|
||||
.state
|
||||
.device
|
||||
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some(&format!("{}_PL", self.name)),
|
||||
bind_group_layouts: &[Some(&bgl)],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = (bgl.clone(), pl.clone());
|
||||
|
||||
cache.insert(sample_types, result.clone());
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns a reference to the vertex shader module.
|
||||
pub fn vertex_module(&self) -> &wgpu::ShaderModule {
|
||||
&self.vertex_module
|
||||
}
|
||||
/// Returns a reference to the fragment shader module.
|
||||
pub fn fragment_module(&self) -> &wgpu::ShaderModule {
|
||||
&self.fragment_module
|
||||
}
|
||||
/// Returns the resource definitions for this program.
|
||||
pub fn resources(&self) -> &[ShaderResourceDefinition] {
|
||||
&self.resources
|
||||
}
|
||||
/// Returns the program name (used for debug labels).
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_generate_wgsl_declarations_textures() {
|
||||
let resources = vec![
|
||||
ShaderResourceDefinition {
|
||||
name: "diffuseTexture".into(),
|
||||
kind: ShaderResourceKind::Texture {
|
||||
kind: SamplerKind::Sampler2D,
|
||||
fallback: Default::default(),
|
||||
},
|
||||
binding: 0,
|
||||
},
|
||||
ShaderResourceDefinition {
|
||||
name: "shadowMap".into(),
|
||||
kind: ShaderResourceKind::Texture {
|
||||
kind: SamplerKind::SamplerCube,
|
||||
fallback: Default::default(),
|
||||
},
|
||||
binding: 1,
|
||||
},
|
||||
];
|
||||
let decls = generate_wgsl_declarations(&resources);
|
||||
assert!(decls.contains("@group(0) @binding(0) var diffuseTexture_tex: texture_2d<f32>;"));
|
||||
assert!(decls.contains("@group(0) @binding(100) var diffuseTexture_samp: sampler;"));
|
||||
assert!(decls.contains("@group(0) @binding(1) var shadowMap_tex: texture_cube<f32>;"));
|
||||
assert!(decls.contains("@group(0) @binding(101) var shadowMap_samp: sampler;"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_wgsl_declarations_uniforms() {
|
||||
use fyrox_graphics::gpu_program::ShaderProperty;
|
||||
let resources = vec![ShaderResourceDefinition {
|
||||
name: "properties".into(),
|
||||
kind: ShaderResourceKind::PropertyGroup(vec![
|
||||
ShaderProperty::new("field0", ShaderPropertyKind::Float { value: 0.0 }),
|
||||
ShaderProperty::new(
|
||||
"field1",
|
||||
ShaderPropertyKind::Vector3 {
|
||||
value: Default::default(),
|
||||
},
|
||||
),
|
||||
ShaderProperty::new("field2", ShaderPropertyKind::Bool { value: false }),
|
||||
]),
|
||||
binding: 0,
|
||||
}];
|
||||
let decls = generate_wgsl_declarations(&resources);
|
||||
assert!(decls.contains("struct Tproperties {"));
|
||||
assert!(decls.contains("field0: f32,"));
|
||||
assert!(decls.contains("field1: vec3f,"));
|
||||
assert!(decls.contains("field2: u32,")); // Bool → u32
|
||||
assert!(decls.contains("@group(0) @binding(200) var<uniform> properties: Tproperties;"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wgsl_texture_type_mapping() {
|
||||
assert_eq!(wgsl_texture_type(SamplerKind::Sampler2D), "texture_2d<f32>");
|
||||
assert_eq!(wgsl_texture_type(SamplerKind::Sampler3D), "texture_3d<f32>");
|
||||
assert_eq!(
|
||||
wgsl_texture_type(SamplerKind::SamplerCube),
|
||||
"texture_cube<f32>"
|
||||
);
|
||||
assert_eq!(
|
||||
wgsl_texture_type(SamplerKind::USampler2D),
|
||||
"texture_2d<u32>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wgsl_property_type_mapping() {
|
||||
assert_eq!(
|
||||
wgsl_property_type(&ShaderPropertyKind::Float { value: 0.0 }),
|
||||
"f32"
|
||||
);
|
||||
assert_eq!(
|
||||
wgsl_property_type(&ShaderPropertyKind::Int { value: 0 }),
|
||||
"i32"
|
||||
);
|
||||
assert_eq!(
|
||||
wgsl_property_type(&ShaderPropertyKind::UInt { value: 0 }),
|
||||
"u32"
|
||||
);
|
||||
assert_eq!(
|
||||
wgsl_property_type(&ShaderPropertyKind::Bool { value: false }),
|
||||
"u32"
|
||||
);
|
||||
assert_eq!(
|
||||
wgsl_property_type(&ShaderPropertyKind::Vector2 {
|
||||
value: Default::default()
|
||||
}),
|
||||
"vec2f"
|
||||
);
|
||||
assert_eq!(
|
||||
wgsl_property_type(&ShaderPropertyKind::Vector3 {
|
||||
value: Default::default()
|
||||
}),
|
||||
"vec3f"
|
||||
);
|
||||
assert_eq!(
|
||||
wgsl_property_type(&ShaderPropertyKind::Vector4 {
|
||||
value: Default::default()
|
||||
}),
|
||||
"vec4f"
|
||||
);
|
||||
assert_eq!(
|
||||
wgsl_property_type(&ShaderPropertyKind::Matrix2 {
|
||||
value: Default::default()
|
||||
}),
|
||||
"mat2x2f"
|
||||
);
|
||||
assert_eq!(
|
||||
wgsl_property_type(&ShaderPropertyKind::Matrix3 {
|
||||
value: Default::default()
|
||||
}),
|
||||
"mat3x3f"
|
||||
);
|
||||
assert_eq!(
|
||||
wgsl_property_type(&ShaderPropertyKind::Matrix4 {
|
||||
value: Default::default()
|
||||
}),
|
||||
"mat4x4f"
|
||||
);
|
||||
assert_eq!(
|
||||
wgsl_property_type(&ShaderPropertyKind::Color {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
a: 255
|
||||
}),
|
||||
"vec4f"
|
||||
);
|
||||
assert_eq!(
|
||||
wgsl_property_type(&ShaderPropertyKind::FloatArray {
|
||||
max_len: 32,
|
||||
value: vec![]
|
||||
}),
|
||||
"array<vec4f, 32>"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use crate::server::WgpuGraphicsServer;
|
||||
use fyrox_graphics::{
|
||||
error::FrameworkError,
|
||||
query::{GpuQueryTrait, QueryKind, QueryResult},
|
||||
};
|
||||
use std::cell::Cell;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Weak;
|
||||
|
||||
/// Wgpu implementation of [`GpuQueryTrait`].
|
||||
///
|
||||
/// **Stub implementation**: wgpu does not currently expose occlusion queries,
|
||||
/// so this type tracks the active state but always returns `u32::MAX` from
|
||||
/// [`try_get_result`](Self::try_get_result). This is sufficient for the engine's
|
||||
/// query-based culling to work (everything is considered visible).
|
||||
pub struct WgpuQuery {
|
||||
_server: Weak<WgpuGraphicsServer>,
|
||||
active: Cell<bool>,
|
||||
}
|
||||
|
||||
impl Debug for WgpuQuery {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("WgpuQuery")
|
||||
.field("active", &self.active.get())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl WgpuQuery {
|
||||
/// Creates a new stub query.
|
||||
pub fn new(server: &WgpuGraphicsServer) -> Result<Self, FrameworkError> {
|
||||
Ok(Self {
|
||||
_server: server.weak_ref(),
|
||||
active: Cell::new(false),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuQueryTrait for WgpuQuery {
|
||||
fn begin(&self, _kind: QueryKind) {
|
||||
self.active.set(true);
|
||||
}
|
||||
fn end(&self) {
|
||||
self.active.set(false);
|
||||
}
|
||||
fn is_started(&self) -> bool {
|
||||
self.active.get()
|
||||
}
|
||||
/// Always returns `Some(QueryResult::SamplesPassed(u32::MAX))` — see the
|
||||
/// struct-level documentation for details.
|
||||
fn try_get_result(&self) -> Option<QueryResult> {
|
||||
Some(QueryResult::SamplesPassed(u32::MAX))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use crate::server::WgpuGraphicsServer;
|
||||
use fyrox_graphics::{
|
||||
core::math::Rect, error::FrameworkError, framebuffer::GpuFrameBufferTrait,
|
||||
gpu_texture::GpuTextureKind, read_buffer::GpuAsyncReadBufferTrait,
|
||||
};
|
||||
use std::cell::Cell;
|
||||
use std::rc::Weak;
|
||||
|
||||
/// Wgpu implementation of [`GpuAsyncReadBufferTrait`].
|
||||
///
|
||||
/// Provides asynchronous pixel readback from a framebuffer's color attachment to
|
||||
/// CPU-accessible memory. Internally uses a `MAP_READ | COPY_DST` buffer and
|
||||
/// `copy_texture_to_buffer` to transfer pixel data.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// 1. Create via [`GraphicsServer::create_async_read_buffer`](fyrox_graphics::server::GraphicsServer::create_async_read_buffer)
|
||||
/// 2. Call [`schedule_pixels_transfer`](Self::schedule_pixels_transfer) to start the transfer
|
||||
/// 3. Poll [`try_read`](Self::try_read) each frame until the data is ready
|
||||
pub struct WgpuAsyncReadBuffer {
|
||||
server: Weak<WgpuGraphicsServer>,
|
||||
buffer: wgpu::Buffer,
|
||||
_pixel_count: usize,
|
||||
pixel_size: usize,
|
||||
request_pending: Cell<bool>,
|
||||
size_bytes: usize,
|
||||
}
|
||||
|
||||
impl WgpuAsyncReadBuffer {
|
||||
/// Creates a new async read buffer with enough capacity for `pixel_count` pixels
|
||||
/// of `pixel_size` bytes each.
|
||||
pub fn new(
|
||||
server: &WgpuGraphicsServer,
|
||||
name: &str,
|
||||
pixel_size: usize,
|
||||
pixel_count: usize,
|
||||
) -> Result<Self, FrameworkError> {
|
||||
let size_bytes = pixel_count * pixel_size;
|
||||
let buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: if server.named_objects {
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
size: size_bytes.max(1) as u64,
|
||||
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
Ok(Self {
|
||||
server: server.weak_ref(),
|
||||
buffer,
|
||||
_pixel_count: pixel_count,
|
||||
pixel_size,
|
||||
request_pending: Cell::new(false),
|
||||
size_bytes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuAsyncReadBufferTrait for WgpuAsyncReadBuffer {
|
||||
fn schedule_pixels_transfer(
|
||||
&self,
|
||||
framebuffer: &dyn GpuFrameBufferTrait,
|
||||
color_buffer_index: u32,
|
||||
_rect: Option<Rect<i32>>,
|
||||
) -> Result<(), FrameworkError> {
|
||||
if self.request_pending.get() {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(server) = self.server.upgrade() else {
|
||||
return Err(FrameworkError::GraphicsServerUnavailable);
|
||||
};
|
||||
let color_attachment = framebuffer
|
||||
.color_attachments()
|
||||
.get(color_buffer_index as usize)
|
||||
.ok_or_else(|| FrameworkError::Custom("No color attachment".into()))?;
|
||||
let wgpu_tex = color_attachment
|
||||
.texture
|
||||
.as_any()
|
||||
.downcast_ref::<crate::texture::WgpuTexture>()
|
||||
.ok_or_else(|| FrameworkError::Custom("Expected WgpuTexture".into()))?;
|
||||
let (w, h) = match color_attachment.texture.kind() {
|
||||
GpuTextureKind::Rectangle { width, height } => (width, height),
|
||||
_ => return Err(FrameworkError::Custom("Only rectangular textures".into())),
|
||||
};
|
||||
let bpp = self.pixel_size;
|
||||
|
||||
let mut encoder = server
|
||||
.state
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||
encoder.copy_texture_to_buffer(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: wgpu_tex.wgpu_texture(),
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
wgpu::TexelCopyBufferInfo {
|
||||
buffer: &self.buffer,
|
||||
layout: wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some((w as u32 * bpp as u32).max(256)),
|
||||
rows_per_image: Some(h as u32),
|
||||
},
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: w as u32,
|
||||
height: h as u32,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
server.state.queue.submit(std::iter::once(encoder.finish()));
|
||||
self.request_pending.set(true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_request_running(&self) -> bool {
|
||||
self.request_pending.get()
|
||||
}
|
||||
|
||||
fn try_read(&self) -> Option<Vec<u8>> {
|
||||
if !self.request_pending.get() {
|
||||
return None;
|
||||
}
|
||||
let server = self.server.upgrade()?;
|
||||
let slice = self.buffer.slice(..self.size_bytes as u64);
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
slice.map_async(wgpu::MapMode::Read, move |r| {
|
||||
tx.send(r).ok();
|
||||
});
|
||||
server
|
||||
.state
|
||||
.device
|
||||
.poll(wgpu::PollType::Wait {
|
||||
submission_index: None,
|
||||
timeout: None,
|
||||
})
|
||||
.ok();
|
||||
match rx.recv() {
|
||||
Ok(Ok(())) => {
|
||||
if let Ok(mapped) = slice.get_mapped_range() {
|
||||
let mut result = vec![0u8; self.size_bytes];
|
||||
result.copy_from_slice(&mapped);
|
||||
drop(mapped);
|
||||
self.buffer.unmap();
|
||||
self.request_pending.set(false);
|
||||
Some(result)
|
||||
} else {
|
||||
self.request_pending.set(false);
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.request_pending.set(false);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use crate::server::WgpuGraphicsServer;
|
||||
use fyrox_graphics::{
|
||||
error::FrameworkError,
|
||||
sampler::{
|
||||
GpuSamplerDescriptor, GpuSamplerTrait, MagnificationFilter, MinificationFilter, WrapMode,
|
||||
},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Weak;
|
||||
|
||||
/// Maps a Fyrox [`MinificationFilter`] to a wgpu [`FilterMode`].
|
||||
///
|
||||
/// Nearest-mipmap variants map to `Nearest`; all others map to `Linear`.
|
||||
fn min_filter_to_wgpu(f: MinificationFilter) -> wgpu::FilterMode {
|
||||
match f {
|
||||
MinificationFilter::Nearest
|
||||
| MinificationFilter::NearestMipMapNearest
|
||||
| MinificationFilter::NearestMipMapLinear => wgpu::FilterMode::Nearest,
|
||||
_ => wgpu::FilterMode::Linear,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a Fyrox [`MagnificationFilter`] to a wgpu [`FilterMode`].
|
||||
fn mag_filter_to_wgpu(f: MagnificationFilter) -> wgpu::FilterMode {
|
||||
match f {
|
||||
MagnificationFilter::Nearest => wgpu::FilterMode::Nearest,
|
||||
_ => wgpu::FilterMode::Linear,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a Fyrox [`MinificationFilter`] to a wgpu [`MipmapFilterMode`].
|
||||
///
|
||||
/// Only `LinearMipMap*` variants produce linear mipmap filtering.
|
||||
fn mipmap_filter_to_wgpu(f: MinificationFilter) -> wgpu::MipmapFilterMode {
|
||||
match f {
|
||||
MinificationFilter::NearestMipMapLinear | MinificationFilter::LinearMipMapLinear => {
|
||||
wgpu::MipmapFilterMode::Linear
|
||||
}
|
||||
_ => wgpu::MipmapFilterMode::Nearest,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a Fyrox [`WrapMode`] to a wgpu [`AddressMode`].
|
||||
fn wrap_mode_to_wgpu(m: WrapMode) -> wgpu::AddressMode {
|
||||
match m {
|
||||
WrapMode::Repeat => wgpu::AddressMode::Repeat,
|
||||
WrapMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,
|
||||
WrapMode::ClampToBorder => wgpu::AddressMode::ClampToBorder,
|
||||
WrapMode::MirroredRepeat | WrapMode::MirrorClampToEdge => wgpu::AddressMode::MirrorRepeat,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wgpu implementation of [`GpuSamplerTrait`].
|
||||
///
|
||||
/// Wraps a [`wgpu::Sampler`] configured from a [`GpuSamplerDescriptor`]. When
|
||||
/// anisotropy is greater than 1, all filters are forced to `Linear` (this is a
|
||||
/// wgpu/WebGPU requirement).
|
||||
pub struct WgpuSampler {
|
||||
_server: Weak<WgpuGraphicsServer>,
|
||||
sampler: wgpu::Sampler,
|
||||
}
|
||||
|
||||
impl Debug for WgpuSampler {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("WgpuSampler").finish()
|
||||
}
|
||||
}
|
||||
impl GpuSamplerTrait for WgpuSampler {}
|
||||
|
||||
impl WgpuSampler {
|
||||
/// Creates a new sampler from the given descriptor.
|
||||
///
|
||||
/// If `anisotropy > 1`, all min/mag/mipmap filters are forced to `Linear`
|
||||
/// regardless of the descriptor settings.
|
||||
pub fn new(
|
||||
server: &WgpuGraphicsServer,
|
||||
desc: GpuSamplerDescriptor,
|
||||
) -> Result<Self, FrameworkError> {
|
||||
let aniso = desc.anisotropy.clamp(1.0, 16.0) as u16;
|
||||
let use_linear = aniso > 1;
|
||||
let sampler = server
|
||||
.state
|
||||
.device
|
||||
.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: if server.named_objects {
|
||||
Some("Sampler")
|
||||
} else {
|
||||
None
|
||||
},
|
||||
address_mode_u: wrap_mode_to_wgpu(desc.s_wrap_mode),
|
||||
address_mode_v: wrap_mode_to_wgpu(desc.t_wrap_mode),
|
||||
address_mode_w: wrap_mode_to_wgpu(desc.r_wrap_mode),
|
||||
mag_filter: if use_linear {
|
||||
wgpu::FilterMode::Linear
|
||||
} else {
|
||||
mag_filter_to_wgpu(desc.mag_filter)
|
||||
},
|
||||
min_filter: if use_linear {
|
||||
wgpu::FilterMode::Linear
|
||||
} else {
|
||||
min_filter_to_wgpu(desc.min_filter)
|
||||
},
|
||||
mipmap_filter: if use_linear {
|
||||
wgpu::MipmapFilterMode::Linear
|
||||
} else {
|
||||
mipmap_filter_to_wgpu(desc.min_filter)
|
||||
},
|
||||
lod_min_clamp: desc.min_lod.max(0.0),
|
||||
lod_max_clamp: desc.max_lod.max(0.0),
|
||||
anisotropy_clamp: aniso,
|
||||
compare: None,
|
||||
border_color: None,
|
||||
});
|
||||
Ok(Self {
|
||||
_server: server.weak_ref(),
|
||||
sampler,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying [`wgpu::Sampler`].
|
||||
pub fn wgpu_sampler(&self) -> &wgpu::Sampler {
|
||||
&self.sampler
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,893 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use crate::buffer::WgpuBuffer;
|
||||
use crate::format_helpers::is_filterable_format;
|
||||
use crate::framebuffer::{PipelineKey, WgpuFrameBuffer};
|
||||
use crate::geometry_buffer::WgpuGeometryBuffer;
|
||||
use crate::program::{WgpuProgram, WgpuShader};
|
||||
use crate::query::WgpuQuery;
|
||||
use crate::read_buffer::WgpuAsyncReadBuffer;
|
||||
use crate::sampler::WgpuSampler;
|
||||
use crate::texture::{texture_size, WgpuTexture};
|
||||
use fyrox_core::futures::executor::block_on;
|
||||
use fyrox_core::log::Log;
|
||||
use fyrox_core::math::Rect;
|
||||
use fyrox_graphics::buffer::{GpuBuffer, GpuBufferDescriptor};
|
||||
use fyrox_graphics::error::FrameworkError;
|
||||
use fyrox_graphics::framebuffer::{Attachment, GpuFrameBuffer};
|
||||
use fyrox_graphics::geometry_buffer::{GpuGeometryBuffer, GpuGeometryBufferDescriptor};
|
||||
use fyrox_graphics::gpu_program::{GpuProgram, GpuShader, ShaderKind, ShaderResourceDefinition};
|
||||
use fyrox_graphics::gpu_texture::{GpuTexture, GpuTextureDescriptor};
|
||||
use fyrox_graphics::query::GpuQuery;
|
||||
use fyrox_graphics::read_buffer::GpuAsyncReadBuffer;
|
||||
use fyrox_graphics::sampler::{GpuSampler, GpuSamplerDescriptor};
|
||||
use fyrox_graphics::server::{
|
||||
GraphicsServer, ServerCapabilities, ServerMemoryUsage, SharedGraphicsServer,
|
||||
};
|
||||
use fyrox_graphics::stats::PipelineStatistics;
|
||||
use fyrox_graphics::{PolygonFace, PolygonFillMode, ScissorBox};
|
||||
use std::cell::{Cell, OnceCell, RefCell};
|
||||
use std::collections::HashMap;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use winit::event_loop::ActiveEventLoop;
|
||||
use winit::window::{Window, WindowAttributes};
|
||||
|
||||
const MIPMAP_SHADER_SRC: &str = r#"
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4<f32> {
|
||||
var pos = array<vec2<f32>, 3>(
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
vec2<f32>( 3.0, -1.0),
|
||||
vec2<f32>(-1.0, 3.0)
|
||||
);
|
||||
return vec4<f32>(pos[idx], 0.0, 1.0);
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var src_tex: texture_2d<f32>;
|
||||
@group(0) @binding(1) var src_sampler: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
return textureLoad(src_tex, vec2<i32>(pos.xy), 0);
|
||||
}
|
||||
"#;
|
||||
|
||||
/// A single draw call recorded into an [`ActivePass`]. Captures all GPU state
|
||||
/// needed to replay the call when the pass is flushed.
|
||||
pub struct DrawCommand {
|
||||
/// The compiled render pipeline for this draw.
|
||||
pub pipeline: wgpu::RenderPipeline,
|
||||
/// Resource bind group (textures, samplers, uniforms).
|
||||
pub bind_group: Option<wgpu::BindGroup>,
|
||||
/// Vertex buffers bound for this draw.
|
||||
pub vertex_buffers: Vec<wgpu::Buffer>,
|
||||
/// Number of extra vertex buffer slots filled with the dummy buffer.
|
||||
pub extra_verts: u32,
|
||||
/// Index buffer for indexed drawing.
|
||||
pub index_buffer: wgpu::Buffer,
|
||||
/// Viewport rectangle.
|
||||
pub viewport: Rect<i32>,
|
||||
/// Stencil reference value, if stencil testing is enabled.
|
||||
pub stencil_ref: Option<u32>,
|
||||
/// Optional scissor clipping rectangle.
|
||||
pub scissor_box: Option<ScissorBox>,
|
||||
/// Start index in the index buffer.
|
||||
pub start_idx: u32,
|
||||
/// End index in the index buffer.
|
||||
pub end_idx: u32,
|
||||
/// Number of instances to draw.
|
||||
pub instances: u32,
|
||||
}
|
||||
|
||||
/// A batched render pass that accumulates draw commands for a single framebuffer.
|
||||
///
|
||||
/// Created when the first draw call targets a framebuffer, and flushed
|
||||
/// (encoded into a `wgpu::RenderPass`) when the target changes or the frame ends.
|
||||
pub struct ActivePass {
|
||||
/// Identity of the target framebuffer (pointer-based).
|
||||
pub framebuffer_id: usize,
|
||||
/// Color attachment views for the render pass.
|
||||
pub color_views: Vec<wgpu::TextureView>,
|
||||
/// Depth-stencil attachment view, if present.
|
||||
pub depth_view: Option<wgpu::TextureView>,
|
||||
/// Load operation for color attachments.
|
||||
pub color_load: wgpu::LoadOp<wgpu::Color>,
|
||||
/// Load operation for the depth attachment.
|
||||
pub depth_load: wgpu::LoadOp<f32>,
|
||||
/// Load operation for the stencil attachment, if present.
|
||||
pub stencil_load: Option<wgpu::LoadOp<u32>>,
|
||||
/// Accumulated draw commands to replay when the pass is flushed.
|
||||
pub commands: Vec<DrawCommand>,
|
||||
}
|
||||
|
||||
/// Core wgpu objects shared across the server.
|
||||
///
|
||||
/// Holds the wgpu instance, adapter, device, and queue. Shared via [`Arc`] so that
|
||||
/// GPU commands can be submitted from any resource type.
|
||||
pub struct WgpuState {
|
||||
/// The wgpu instance (entry point for creating adapters and surfaces).
|
||||
pub instance: wgpu::Instance,
|
||||
/// The selected physical device.
|
||||
pub adapter: wgpu::Adapter,
|
||||
/// The logical device for creating GPU resources.
|
||||
pub device: wgpu::Device,
|
||||
/// The command queue for submitting GPU work.
|
||||
pub queue: wgpu::Queue,
|
||||
}
|
||||
|
||||
/// The main wgpu-based graphics server.
|
||||
///
|
||||
/// Implements [`GraphicsServer`] and serves
|
||||
/// as the entry point for all GPU resource creation. Manages the wgpu device, surface,
|
||||
/// pipeline cache, and memory usage tracking.
|
||||
///
|
||||
/// # Pipeline Cache
|
||||
///
|
||||
/// Render pipelines are cached by a hash of [`PipelineKey`] to avoid recreating
|
||||
/// identical pipelines across draw calls. The cache is stored in a [`RefCell<HashMap>`]
|
||||
/// and grows monotonically during the session.
|
||||
///
|
||||
/// # Bind Group Cache
|
||||
///
|
||||
/// Bind groups are cached by a hash of resource pointers and texture formats to avoid
|
||||
/// recreating identical bind groups across draw calls. This is critical for performance
|
||||
/// since bind group creation is expensive.
|
||||
///
|
||||
/// # Backbuffer Lifecycle
|
||||
///
|
||||
/// The backbuffer acquires a surface texture on the first draw call per frame.
|
||||
/// Multiple draw calls to the backbuffer reuse the same texture. The frame is
|
||||
/// presented via [`swap_buffers`](Self::swap_buffers), which also sets the
|
||||
/// clear flag for the next frame.
|
||||
pub struct WgpuGraphicsServer {
|
||||
/// Core wgpu objects (instance, adapter, device, queue).
|
||||
pub state: Arc<WgpuState>,
|
||||
/// The rendering surface (window or canvas).
|
||||
pub surface: wgpu::Surface<'static>,
|
||||
/// Current surface configuration (format, size, present mode).
|
||||
pub surface_config: RwLock<wgpu::SurfaceConfiguration>,
|
||||
/// Whether to set debug labels on GPU objects.
|
||||
pub named_objects: bool,
|
||||
/// MSAA sample count (currently forced to 1).
|
||||
pub msaa_sample_count: u32,
|
||||
/// Hash-based cache of render pipelines, keyed by [`PipelineKey`].
|
||||
pub pipeline_cache: RefCell<HashMap<PipelineKey, wgpu::RenderPipeline>>,
|
||||
/// Hash-based cache of bind groups, keyed by resource pointers and texture formats.
|
||||
pub bind_group_cache: RefCell<HashMap<u64, wgpu::BindGroup>>,
|
||||
weak_self: RefCell<Option<Weak<WgpuGraphicsServer>>>,
|
||||
/// Tracked GPU memory usage (buffers + textures).
|
||||
pub memory_usage: RefCell<ServerMemoryUsage>,
|
||||
pipeline_statistics: RefCell<PipelineStatistics>,
|
||||
/// Small buffer bound to extra vertex slots when geometry lacks attributes the shader expects.
|
||||
pub dummy_vertex_buffer: wgpu::Buffer,
|
||||
/// Non-filtering sampler for textures with non-filterable formats (e.g. R32Float).
|
||||
non_filtering_sampler: wgpu::Sampler,
|
||||
/// Holds the acquired surface frame between do_draw and swap_buffers.
|
||||
pub current_frame: RefCell<Option<wgpu::SurfaceTexture>>,
|
||||
/// Whether the backbuffer needs clearing at the start of the next frame.
|
||||
pub backbuffer_needs_clear: Cell<bool>,
|
||||
/// Cached depth-stencil texture for the backbuffer, with its (width, height).
|
||||
backbuffer_depth_stencil: RefCell<Option<(u32, u32, GpuTexture)>>,
|
||||
/// Per-frame command encoder. Lazily created on first draw, submitted in swap_buffers.
|
||||
/// Storing it on the server (not per-framebuffer) because multiple framebuffers
|
||||
/// (G-Buffer, HDR, backbuffer) share the same frame and benefit from a single submit.
|
||||
pub frame_encoder: RefCell<Option<wgpu::CommandEncoder>>,
|
||||
/// Currently accumulating render pass. Draw commands are batched here and
|
||||
/// flushed when the target changes or the frame ends.
|
||||
pub active_pass: RefCell<Option<ActivePass>>,
|
||||
/// Sampler used for mipmap generation (linear filtering).
|
||||
mipmap_sampler: wgpu::Sampler,
|
||||
/// Bind group layout for the mipmap generation shader.
|
||||
mipmap_bind_group_layout: wgpu::BindGroupLayout,
|
||||
/// Cached mipmap render pipelines, keyed by texture format.
|
||||
mipmap_pipeline_cache: RefCell<HashMap<wgpu::TextureFormat, wgpu::RenderPipeline>>,
|
||||
/// Shared shader module for mipmap generation (created once).
|
||||
mipmap_shader: OnceCell<wgpu::ShaderModule>,
|
||||
/// Shared pipeline layout for mipmap generation (created once).
|
||||
mipmap_pipeline_layout: OnceCell<wgpu::PipelineLayout>,
|
||||
/// Current polygon fill mode (Fill, Line, or Point). Baked into new pipelines.
|
||||
polygon_fill_mode: Cell<PolygonFillMode>,
|
||||
}
|
||||
|
||||
impl WgpuGraphicsServer {
|
||||
/// Creates a new wgpu graphics server with a window and GPU device.
|
||||
///
|
||||
/// Initializes wgpu with the primary backend (Vulkan/Metal/DX12 on native,
|
||||
/// WebGL2 on WASM). Prefers non-sRGB surface formats to avoid double gamma
|
||||
/// correction (the engine applies its own in the HDR tone-mapping pass).
|
||||
///
|
||||
/// Returns the created [`Window`] and a shared [`GraphicsServer`] handle.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vsync` — enable vertical sync (`PresentMode::AutoVsync` vs `AutoNoVsync`)
|
||||
/// * `_msaa_sample_count` — currently ignored (MSAA not yet implemented)
|
||||
/// * `window_target` — the winit event loop for window/surface creation
|
||||
/// * `window_attributes` — initial window configuration
|
||||
/// * `named_objects` — whether to set debug labels on GPU objects
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(
|
||||
vsync: bool,
|
||||
_msaa_sample_count: Option<u8>,
|
||||
window_target: &ActiveEventLoop,
|
||||
window_attributes: WindowAttributes,
|
||||
named_objects: bool,
|
||||
) -> Result<(Window, SharedGraphicsServer), FrameworkError> {
|
||||
let window = window_target
|
||||
.create_window(window_attributes)
|
||||
.map_err(|e| FrameworkError::Custom(format!("Failed to create window: {e}")))?;
|
||||
let size = window.inner_size();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::PRIMARY,
|
||||
..wgpu::InstanceDescriptor::new_with_display_handle(Box::new(
|
||||
window_target.owned_display_handle(),
|
||||
))
|
||||
});
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::GL,
|
||||
..wgpu::InstanceDescriptor::new_without_display_handle()
|
||||
});
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let surface = unsafe {
|
||||
let target = wgpu::SurfaceTargetUnsafe::from_window(&window)
|
||||
.map_err(|e| FrameworkError::Custom(format!("Failed to get window handle: {e}")))?;
|
||||
instance
|
||||
.create_surface_unsafe(target)
|
||||
.map_err(|e| FrameworkError::Custom(format!("Failed to create surface: {e}")))?
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let surface = {
|
||||
use fyrox_core::wasm_bindgen::JsCast;
|
||||
use winit::platform::web::WindowExtWebSys;
|
||||
let canvas = window.canvas().unwrap();
|
||||
let web_window = fyrox_core::web_sys::window().unwrap();
|
||||
let document = web_window.document().unwrap();
|
||||
let body = document.body().unwrap();
|
||||
body.append_child(&canvas)
|
||||
.expect("Append canvas to HTML body");
|
||||
instance
|
||||
.create_surface(wgpu::SurfaceTarget::Canvas(canvas))
|
||||
.map_err(|e| FrameworkError::Custom(format!("Failed to create surface: {e}")))?
|
||||
};
|
||||
|
||||
let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
apply_limit_buckets: false,
|
||||
}))
|
||||
.map_err(|e| FrameworkError::Custom(format!("No suitable WGPU adapter found: {e}")))?;
|
||||
|
||||
let adapter_features = adapter.features();
|
||||
let mut required_features = wgpu::Features::empty();
|
||||
|
||||
if adapter_features.contains(wgpu::Features::POLYGON_MODE_LINE) {
|
||||
required_features |= wgpu::Features::POLYGON_MODE_LINE;
|
||||
}
|
||||
|
||||
let (device, queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
required_features,
|
||||
required_limits: if cfg!(target_arch = "wasm32") {
|
||||
wgpu::Limits::downlevel_webgl2_defaults()
|
||||
} else {
|
||||
adapter.limits()
|
||||
},
|
||||
memory_hints: wgpu::MemoryHints::Performance,
|
||||
..Default::default()
|
||||
}))
|
||||
.map_err(|e| FrameworkError::Custom(format!("Failed to request device: {e}")))?;
|
||||
|
||||
let surface_caps = surface.get_capabilities(&adapter);
|
||||
// Prefer linear (non-sRGB) formats to avoid double gamma correction.
|
||||
// The engine applies its own gamma correction in the HDR tone-mapping pass.
|
||||
let surface_format = surface_caps
|
||||
.formats
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|f| !f.is_srgb())
|
||||
.or_else(|| surface_caps.formats.first().copied())
|
||||
.ok_or_else(|| FrameworkError::Custom("Surface has no supported formats".into()))?;
|
||||
|
||||
let present_mode = if vsync {
|
||||
wgpu::PresentMode::AutoVsync
|
||||
} else {
|
||||
wgpu::PresentMode::AutoNoVsync
|
||||
};
|
||||
|
||||
let surface_config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: surface_format,
|
||||
color_space: Default::default(),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode,
|
||||
alpha_mode: surface_caps.alpha_modes[0],
|
||||
view_formats: vec![],
|
||||
desired_maximum_frame_latency: 2,
|
||||
};
|
||||
surface.configure(&device, &surface_config);
|
||||
|
||||
// TODO: Force `msaa_sample_count` to 1 in the wgpu backend. Full MSAA support requires creating multisampled render targets and resolve targets, which is a larger feature.
|
||||
let msaa = 1u32; // msaa_sample_count.unwrap_or(1).max(1) as u32;
|
||||
|
||||
let non_filtering_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("NonFilteringSampler"),
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mipmap_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("MipmapSampler"),
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mipmap_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("MipmapBGL"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let dummy_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("DummyVB"),
|
||||
size: 16, // enough for vec4f
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let server = Rc::new(Self {
|
||||
state: Arc::new(WgpuState {
|
||||
instance,
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
}),
|
||||
surface,
|
||||
surface_config: RwLock::new(surface_config),
|
||||
named_objects,
|
||||
msaa_sample_count: msaa,
|
||||
pipeline_cache: RefCell::new(HashMap::new()),
|
||||
bind_group_cache: RefCell::new(HashMap::new()),
|
||||
weak_self: RefCell::new(None),
|
||||
memory_usage: RefCell::new(ServerMemoryUsage::default()),
|
||||
pipeline_statistics: RefCell::new(PipelineStatistics::default()),
|
||||
dummy_vertex_buffer,
|
||||
non_filtering_sampler,
|
||||
current_frame: RefCell::new(None),
|
||||
backbuffer_needs_clear: Cell::new(true),
|
||||
backbuffer_depth_stencil: RefCell::new(None),
|
||||
frame_encoder: RefCell::new(None),
|
||||
active_pass: RefCell::new(None),
|
||||
mipmap_sampler,
|
||||
mipmap_bind_group_layout,
|
||||
mipmap_pipeline_cache: RefCell::new(HashMap::new()),
|
||||
mipmap_shader: OnceCell::new(),
|
||||
mipmap_pipeline_layout: OnceCell::new(),
|
||||
polygon_fill_mode: Cell::new(PolygonFillMode::Fill),
|
||||
});
|
||||
|
||||
*server.weak_self.borrow_mut() = Some(Rc::downgrade(&server));
|
||||
|
||||
Ok((window, server))
|
||||
}
|
||||
|
||||
/// Returns a [`Weak`] reference to this server.
|
||||
///
|
||||
/// Used by resource types to avoid reference cycles. Resources store a weak
|
||||
/// reference and call [`upgrade`](Weak::upgrade) when they need the server.
|
||||
pub fn weak_ref(&self) -> Weak<WgpuGraphicsServer> {
|
||||
self.weak_self.borrow().clone().unwrap()
|
||||
}
|
||||
/// Returns the non-filtering sampler used for textures with non-filterable formats
|
||||
/// (e.g. `R32Float`, `R32Uint`). These formats require
|
||||
/// [`SamplerBindingType::NonFiltering`](wgpu::SamplerBindingType::NonFiltering).
|
||||
pub fn non_filtering_sampler(&self) -> &wgpu::Sampler {
|
||||
&self.non_filtering_sampler
|
||||
}
|
||||
/// Returns the current polygon fill mode. Baked into new render pipelines.
|
||||
pub fn polygon_fill_mode(&self) -> PolygonFillMode {
|
||||
self.polygon_fill_mode.get()
|
||||
}
|
||||
|
||||
/// Encodes the current [`ActivePass`] into the frame command encoder and clears it.
|
||||
///
|
||||
/// All buffered draw commands are replayed into a `wgpu::RenderPass`, which is
|
||||
/// then dropped (ending the pass). The encoder is stored back for subsequent
|
||||
/// operations. Does nothing if there is no active pass.
|
||||
pub fn flush_active_pass(&self) {
|
||||
let Some(pass) = self.active_pass.borrow_mut().take() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut encoder = self.frame_encoder.borrow_mut().take().unwrap_or_else(|| {
|
||||
self.state
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None })
|
||||
});
|
||||
|
||||
let color_attachments: Vec<_> = pass
|
||||
.color_views
|
||||
.iter()
|
||||
.map(|view| {
|
||||
Some(wgpu::RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
depth_slice: None,
|
||||
ops: wgpu::Operations {
|
||||
load: pass.color_load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
color_attachments: &color_attachments,
|
||||
depth_stencil_attachment: pass.depth_view.as_ref().map(|v| {
|
||||
wgpu::RenderPassDepthStencilAttachment {
|
||||
view: v,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: pass.depth_load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: pass.stencil_load.map(|load| wgpu::Operations {
|
||||
load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
}
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
for cmd in pass.commands {
|
||||
rp.set_viewport(
|
||||
cmd.viewport.x() as f32,
|
||||
cmd.viewport.y() as f32,
|
||||
cmd.viewport.w() as f32,
|
||||
cmd.viewport.h() as f32,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
rp.set_pipeline(&cmd.pipeline);
|
||||
if let Some(bg) = &cmd.bind_group {
|
||||
rp.set_bind_group(0, bg, &[]);
|
||||
}
|
||||
if let Some(st) = cmd.stencil_ref {
|
||||
rp.set_stencil_reference(st);
|
||||
}
|
||||
match cmd.scissor_box {
|
||||
Some(sb) => {
|
||||
// The ScissorBox Y is computed for OpenGL (origin at bottom-left):
|
||||
// y_gl = viewport_h - (pos_y + size_h)
|
||||
// wgpu uses top-left origin (same as UI coords), so convert:
|
||||
// y_wgpu = viewport_h - y_gl - height = pos_y
|
||||
let rt_h = cmd.viewport.h();
|
||||
let wgpu_y = (rt_h - sb.y - sb.height).max(0);
|
||||
rp.set_scissor_rect(
|
||||
sb.x.max(0) as u32,
|
||||
wgpu_y as u32,
|
||||
sb.width.max(0) as u32,
|
||||
sb.height.max(0) as u32,
|
||||
);
|
||||
}
|
||||
None => rp.set_scissor_rect(
|
||||
cmd.viewport.x().max(0) as u32,
|
||||
cmd.viewport.y().max(0) as u32,
|
||||
cmd.viewport.w().max(0) as u32,
|
||||
cmd.viewport.h().max(0) as u32,
|
||||
),
|
||||
}
|
||||
for (i, vb) in cmd.vertex_buffers.iter().enumerate() {
|
||||
rp.set_vertex_buffer(i as u32, vb.slice(..));
|
||||
}
|
||||
let geo_buf_count = cmd.vertex_buffers.len() as u32;
|
||||
for i in 0..cmd.extra_verts {
|
||||
rp.set_vertex_buffer(geo_buf_count + i, self.dummy_vertex_buffer.slice(..));
|
||||
}
|
||||
rp.set_index_buffer(cmd.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
|
||||
rp.draw_indexed(cmd.start_idx..cmd.end_idx, 0, 0..cmd.instances);
|
||||
}
|
||||
|
||||
drop(rp);
|
||||
*self.frame_encoder.borrow_mut() = Some(encoder);
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicsServer for WgpuGraphicsServer {
|
||||
fn create_buffer(&self, desc: GpuBufferDescriptor) -> Result<GpuBuffer, FrameworkError> {
|
||||
Ok(GpuBuffer(Rc::new(WgpuBuffer::new(self, desc)?)))
|
||||
}
|
||||
fn create_texture(&self, desc: GpuTextureDescriptor) -> Result<GpuTexture, FrameworkError> {
|
||||
Ok(GpuTexture(Rc::new(WgpuTexture::new(self, desc)?)))
|
||||
}
|
||||
fn create_sampler(&self, desc: GpuSamplerDescriptor) -> Result<GpuSampler, FrameworkError> {
|
||||
Ok(GpuSampler(Rc::new(WgpuSampler::new(self, desc)?)))
|
||||
}
|
||||
fn create_frame_buffer(
|
||||
&self,
|
||||
depth: Option<Attachment>,
|
||||
colors: Vec<Attachment>,
|
||||
) -> Result<GpuFrameBuffer, FrameworkError> {
|
||||
Ok(GpuFrameBuffer(Rc::new(WgpuFrameBuffer::new(
|
||||
self, depth, colors,
|
||||
)?)))
|
||||
}
|
||||
fn back_buffer(&self) -> GpuFrameBuffer {
|
||||
let config = self.surface_config.read().unwrap();
|
||||
let (w, h) = (config.width, config.height);
|
||||
drop(config);
|
||||
|
||||
let mut cache = self.backbuffer_depth_stencil.borrow_mut();
|
||||
let needs_recreate = match cache.as_ref() {
|
||||
Some((cw, ch, _)) => *cw != w || *ch != h,
|
||||
None => true,
|
||||
};
|
||||
if needs_recreate {
|
||||
if w > 0 && h > 0 {
|
||||
match self.create_2d_render_target(
|
||||
"BackbufferDepthStencil",
|
||||
fyrox_graphics::gpu_texture::PixelKind::D24S8,
|
||||
w as usize,
|
||||
h as usize,
|
||||
) {
|
||||
Ok(tex) => {
|
||||
*cache = Some((w, h, tex));
|
||||
}
|
||||
Err(e) => {
|
||||
Log::warn(format!("Failed to create backbuffer depth-stencil: {e}"));
|
||||
*cache = None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*cache = None;
|
||||
}
|
||||
}
|
||||
let depth_attachment = cache
|
||||
.as_ref()
|
||||
.map(|(_, _, tex)| Attachment::depth_stencil(tex.clone()));
|
||||
GpuFrameBuffer(Rc::new(WgpuFrameBuffer::backbuffer(self, depth_attachment)))
|
||||
}
|
||||
fn create_query(&self) -> Result<GpuQuery, FrameworkError> {
|
||||
Ok(GpuQuery(Rc::new(WgpuQuery::new(self)?)))
|
||||
}
|
||||
fn create_shader(
|
||||
&self,
|
||||
name: String,
|
||||
kind: ShaderKind,
|
||||
source: String,
|
||||
resources: &[ShaderResourceDefinition],
|
||||
line_offset: isize,
|
||||
) -> Result<GpuShader, FrameworkError> {
|
||||
Ok(GpuShader(Rc::new(WgpuShader::new(
|
||||
self,
|
||||
name,
|
||||
kind,
|
||||
source,
|
||||
resources,
|
||||
line_offset,
|
||||
)?)))
|
||||
}
|
||||
fn create_program(
|
||||
&self,
|
||||
name: &str,
|
||||
vs: String,
|
||||
vs_offset: isize,
|
||||
fs: String,
|
||||
fs_offset: isize,
|
||||
resources: &[ShaderResourceDefinition],
|
||||
) -> Result<GpuProgram, FrameworkError> {
|
||||
Ok(GpuProgram(Rc::new(WgpuProgram::from_source(
|
||||
self, name, vs, vs_offset, fs, fs_offset, resources,
|
||||
)?)))
|
||||
}
|
||||
fn create_program_from_shaders(
|
||||
&self,
|
||||
name: &str,
|
||||
vs: &GpuShader,
|
||||
fs: &GpuShader,
|
||||
resources: &[ShaderResourceDefinition],
|
||||
) -> Result<GpuProgram, FrameworkError> {
|
||||
Ok(GpuProgram(Rc::new(WgpuProgram::from_shaders(
|
||||
self, name, vs, fs, resources,
|
||||
)?)))
|
||||
}
|
||||
fn create_async_read_buffer(
|
||||
&self,
|
||||
name: &str,
|
||||
pixel_size: usize,
|
||||
pixel_count: usize,
|
||||
) -> Result<GpuAsyncReadBuffer, FrameworkError> {
|
||||
Ok(GpuAsyncReadBuffer(Rc::new(WgpuAsyncReadBuffer::new(
|
||||
self,
|
||||
name,
|
||||
pixel_size,
|
||||
pixel_count,
|
||||
)?)))
|
||||
}
|
||||
fn create_geometry_buffer(
|
||||
&self,
|
||||
desc: GpuGeometryBufferDescriptor,
|
||||
) -> Result<GpuGeometryBuffer, FrameworkError> {
|
||||
Ok(GpuGeometryBuffer(Rc::new(WgpuGeometryBuffer::new(
|
||||
self, desc,
|
||||
)?)))
|
||||
}
|
||||
fn weak(&self) -> Weak<dyn GraphicsServer> {
|
||||
self.weak_ref() as Weak<dyn GraphicsServer>
|
||||
}
|
||||
fn flush(&self) {
|
||||
// flush in Fyrox means "send the accumulated commands to the video card right now."
|
||||
// An empty submit is not needed here, just close and send the encoder, if there is one.
|
||||
self.flush_active_pass();
|
||||
if let Some(encoder) = self.frame_encoder.borrow_mut().take() {
|
||||
self.state.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
}
|
||||
fn finish(&self) {
|
||||
self.state
|
||||
.device
|
||||
.poll(wgpu::PollType::Wait {
|
||||
submission_index: None,
|
||||
timeout: None,
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
fn invalidate_resource_bindings_cache(&self) {
|
||||
*self.pipeline_statistics.borrow_mut() = Default::default();
|
||||
}
|
||||
fn pipeline_statistics(&self) -> PipelineStatistics {
|
||||
*self.pipeline_statistics.borrow()
|
||||
}
|
||||
fn swap_buffers(&self) -> Result<(), FrameworkError> {
|
||||
// Submit all batched draw commands from this frame
|
||||
self.flush_active_pass();
|
||||
if let Some(encoder) = self.frame_encoder.borrow_mut().take() {
|
||||
self.state.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
|
||||
if let Some(frame) = self.current_frame.borrow_mut().take() {
|
||||
self.state.queue.present(frame);
|
||||
}
|
||||
|
||||
self.backbuffer_needs_clear.replace(true);
|
||||
Ok(())
|
||||
}
|
||||
fn set_frame_size(&self, new_size: (u32, u32)) {
|
||||
if new_size.0 > 0 && new_size.1 > 0 {
|
||||
let mut config = self.surface_config.write().unwrap();
|
||||
|
||||
if config.width == new_size.0 && config.height == new_size.1 {
|
||||
return;
|
||||
}
|
||||
|
||||
config.width = new_size.0;
|
||||
config.height = new_size.1;
|
||||
|
||||
self.flush_active_pass();
|
||||
if let Some(encoder) = self.frame_encoder.borrow_mut().take() {
|
||||
self.state.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
|
||||
self.current_frame.borrow_mut().take();
|
||||
|
||||
self.surface.configure(&self.state.device, &config);
|
||||
}
|
||||
}
|
||||
fn capabilities(&self) -> ServerCapabilities {
|
||||
let limits = self.state.device.limits();
|
||||
ServerCapabilities {
|
||||
max_uniform_buffer_binding_size: limits.max_uniform_buffer_binding_size as usize,
|
||||
uniform_buffer_offset_alignment: limits.min_uniform_buffer_offset_alignment as usize,
|
||||
max_lod_bias: 16.0,
|
||||
}
|
||||
}
|
||||
fn set_polygon_fill_mode(&self, _face: PolygonFace, mode: PolygonFillMode) {
|
||||
self.polygon_fill_mode.set(mode);
|
||||
}
|
||||
fn generate_mipmap(&self, texture: &GpuTexture) {
|
||||
let Some(wtex) = texture.as_any().downcast_ref::<WgpuTexture>() else {
|
||||
return;
|
||||
};
|
||||
let format = wtex.format();
|
||||
if !is_filterable_format(format) {
|
||||
Log::warn("generate_mipmap: format is not filterable, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
let (width, height, _depth) = texture_size(texture.kind());
|
||||
let mip_count = wtex.wgpu_texture().mip_level_count();
|
||||
if mip_count <= 1 || width <= 1 || height <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.flush_active_pass();
|
||||
|
||||
let shader = self.mipmap_shader.get_or_init(|| {
|
||||
self.state
|
||||
.device
|
||||
.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("MipmapShader"),
|
||||
source: wgpu::ShaderSource::Wgsl(MIPMAP_SHADER_SRC.into()),
|
||||
})
|
||||
});
|
||||
let layout = self.mipmap_pipeline_layout.get_or_init(|| {
|
||||
self.state
|
||||
.device
|
||||
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("MipmapPL"),
|
||||
bind_group_layouts: &[Some(&self.mipmap_bind_group_layout)],
|
||||
..Default::default()
|
||||
})
|
||||
});
|
||||
|
||||
if !self.mipmap_pipeline_cache.borrow().contains_key(&format) {
|
||||
let pipeline =
|
||||
self.state
|
||||
.device
|
||||
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("MipmapPipeline"),
|
||||
layout: Some(layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: Default::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
self.mipmap_pipeline_cache
|
||||
.borrow_mut()
|
||||
.insert(format, pipeline);
|
||||
}
|
||||
|
||||
let pipeline_cache = self.mipmap_pipeline_cache.borrow();
|
||||
let pipeline = pipeline_cache.get(&format).unwrap();
|
||||
|
||||
let mut encoder = self.frame_encoder.borrow_mut().take().unwrap_or_else(|| {
|
||||
self.state
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None })
|
||||
});
|
||||
|
||||
let mut mip_w = width;
|
||||
let mut mip_h = height;
|
||||
|
||||
for level in 1..mip_count {
|
||||
mip_w = (mip_w / 2).max(1);
|
||||
mip_h = (mip_h / 2).max(1);
|
||||
|
||||
let src_view = wtex
|
||||
.wgpu_texture()
|
||||
.create_view(&wgpu::TextureViewDescriptor {
|
||||
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||
base_mip_level: level - 1,
|
||||
mip_level_count: Some(1),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let dst_view = wtex
|
||||
.wgpu_texture()
|
||||
.create_view(&wgpu::TextureViewDescriptor {
|
||||
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||
base_mip_level: level,
|
||||
mip_level_count: Some(1),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let bind_group = self
|
||||
.state
|
||||
.device
|
||||
.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: None,
|
||||
layout: &self.mipmap_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&src_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&self.mipmap_sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &dst_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
depth_slice: None,
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
rp.set_viewport(0.0, 0.0, mip_w as f32, mip_h as f32, 0.0, 1.0);
|
||||
rp.set_pipeline(pipeline);
|
||||
rp.set_bind_group(0, &bind_group, &[]);
|
||||
rp.draw(0..3, 0..1);
|
||||
drop(rp);
|
||||
}
|
||||
|
||||
*self.frame_encoder.borrow_mut() = Some(encoder);
|
||||
}
|
||||
fn memory_usage(&self) -> ServerMemoryUsage {
|
||||
self.memory_usage.borrow().clone()
|
||||
}
|
||||
fn push_debug_group(&self, _name: &str) {}
|
||||
fn pop_debug_group(&self) {}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
// Shared functions for all shaders in the engine.
|
||||
// WGSL version for wgpu backend.
|
||||
|
||||
const PI: f32 = 3.14159;
|
||||
|
||||
fn inverse_mat4(m: mat4x4f) -> mat4x4f {
|
||||
let m00 = m[0][0]; let m01 = m[0][1]; let m02 = m[0][2]; let m03 = m[0][3];
|
||||
let m10 = m[1][0]; let m11 = m[1][1]; let m12 = m[1][2]; let m13 = m[1][3];
|
||||
let m20 = m[2][0]; let m21 = m[2][1]; let m22 = m[2][2]; let m23 = m[2][3];
|
||||
let m30 = m[3][0]; let m31 = m[3][1]; let m32 = m[3][2]; let m33 = m[3][3];
|
||||
|
||||
let b00 = m00 * m11 - m01 * m10;
|
||||
let b01 = m00 * m12 - m02 * m10;
|
||||
let b02 = m00 * m13 - m03 * m10;
|
||||
let b03 = m01 * m12 - m02 * m11;
|
||||
let b04 = m01 * m13 - m03 * m11;
|
||||
let b05 = m02 * m13 - m03 * m12;
|
||||
let b06 = m20 * m31 - m21 * m30;
|
||||
let b07 = m20 * m32 - m22 * m30;
|
||||
let b08 = m20 * m33 - m23 * m30;
|
||||
let b09 = m21 * m32 - m22 * m31;
|
||||
let b10 = m21 * m33 - m23 * m31;
|
||||
let b11 = m22 * m33 - m23 * m32;
|
||||
|
||||
var inv = mat4x4f();
|
||||
inv[0][0] = m11 * b11 - m12 * b10 + m13 * b09;
|
||||
inv[0][1] = m02 * b10 - m01 * b11 - m03 * b09;
|
||||
inv[0][2] = m31 * b05 - m32 * b04 + m33 * b03;
|
||||
inv[0][3] = m22 * b04 - m21 * b05 - m23 * b03;
|
||||
inv[1][0] = m12 * b08 - m10 * b11 - m13 * b07;
|
||||
inv[1][1] = m00 * b11 - m02 * b08 + m03 * b07;
|
||||
inv[1][2] = m32 * b02 - m30 * b05 - m33 * b01;
|
||||
inv[1][3] = m20 * b05 - m22 * b02 + m23 * b01;
|
||||
inv[2][0] = m10 * b10 - m11 * b08 + m13 * b06;
|
||||
inv[2][1] = m01 * b08 - m00 * b10 - m03 * b06;
|
||||
inv[2][2] = m30 * b04 - m31 * b02 + m33 * b00;
|
||||
inv[2][3] = m21 * b02 - m20 * b04 - m23 * b00;
|
||||
inv[3][0] = m11 * b07 - m10 * b09 - m12 * b06;
|
||||
inv[3][1] = m00 * b09 - m01 * b07 + m02 * b06;
|
||||
inv[3][2] = m31 * b01 - m30 * b03 - m32 * b00;
|
||||
inv[3][3] = m20 * b03 - m21 * b01 + m22 * b00;
|
||||
|
||||
let det = m00 * b00 - m01 * b01 + m02 * b02 - m03 * b03;
|
||||
let invDet = 1.0 / det;
|
||||
return inv * invDet;
|
||||
}
|
||||
|
||||
fn S_SolveQuadraticEq(a: f32, b: f32, c: f32, minT: ptr<function, f32>, maxT: ptr<function, f32>) -> bool {
|
||||
let twoA = 2.0 * a;
|
||||
let det = b * b - 2.0 * twoA * c;
|
||||
if (det < 0.0) { *minT = 0.0; *maxT = 0.0; return false; }
|
||||
let sqrtDet = sqrt(det);
|
||||
let root1 = (-b - sqrtDet) / twoA;
|
||||
let root2 = (-b + sqrtDet) / twoA;
|
||||
*minT = min(root1, root2);
|
||||
*maxT = max(root1, root2);
|
||||
return true;
|
||||
}
|
||||
|
||||
fn S_LightDistanceAttenuation(distance: f32, radius: f32) -> f32 {
|
||||
return clamp(1.0 - distance * distance / (radius * radius), 0.0, 1.0);
|
||||
}
|
||||
|
||||
fn S_Project(worldPosition: vec3f, matrix: mat4x4f) -> vec3f {
|
||||
let screenPos = matrix * vec4f(worldPosition, 1.0);
|
||||
return (screenPos.xyz / screenPos.w) * 0.5 + 0.5;
|
||||
}
|
||||
|
||||
fn S_UnProject(screenPos: vec3f, matrix: mat4x4f) -> vec3f {
|
||||
let clipSpacePos = vec4f(screenPos * 2.0 - 1.0, 1.0);
|
||||
let position = matrix * clipSpacePos;
|
||||
return position.xyz / position.w;
|
||||
}
|
||||
|
||||
fn S_DistributionGGX(N: vec3f, H: vec3f, roughness: f32) -> f32 {
|
||||
let a = roughness * roughness;
|
||||
let a2 = a * a;
|
||||
let NdotH = max(dot(N, H), 0.0);
|
||||
let NdotH2 = NdotH * NdotH;
|
||||
let nom = a2;
|
||||
var denom = (NdotH2 * (a2 - 1.0) + 1.0);
|
||||
denom = PI * denom * denom;
|
||||
return nom / denom;
|
||||
}
|
||||
|
||||
fn S_GeometrySchlickGGX(NdotV: f32, roughness: f32) -> f32 {
|
||||
let r = (roughness + 1.0);
|
||||
let k = (r * r) / 8.0;
|
||||
let nom = NdotV;
|
||||
let denom = NdotV * (1.0 - k) + k;
|
||||
return nom / denom;
|
||||
}
|
||||
|
||||
fn S_GeometrySmith(N: vec3f, V: vec3f, L: vec3f, roughness: f32) -> f32 {
|
||||
let NdotV = max(dot(N, V), 0.0);
|
||||
let NdotL = max(dot(N, L), 0.0);
|
||||
let ggx2 = S_GeometrySchlickGGX(NdotV, roughness);
|
||||
let ggx1 = S_GeometrySchlickGGX(NdotL, roughness);
|
||||
return ggx1 * ggx2;
|
||||
}
|
||||
|
||||
fn S_FresnelSchlick(cosTheta: f32, F0: vec3f) -> vec3f {
|
||||
return F0 + (1.0 - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0);
|
||||
}
|
||||
|
||||
fn S_FresnelSchlickRoughness(cosTheta: f32, F0: vec3f, roughness: f32) -> vec3f {
|
||||
return F0 + (max(vec3f(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
|
||||
}
|
||||
|
||||
struct TPBRContext {
|
||||
lightColor: vec3f,
|
||||
viewVector: vec3f,
|
||||
fragmentToLight: vec3f,
|
||||
fragmentNormal: vec3f,
|
||||
metallic: f32,
|
||||
roughness: f32,
|
||||
albedo: vec3f,
|
||||
};
|
||||
|
||||
fn S_PBR_CalculateLight(ctx: TPBRContext) -> vec3f {
|
||||
let F0 = mix(vec3f(0.04), ctx.albedo, ctx.metallic);
|
||||
let L = ctx.fragmentToLight;
|
||||
let H = normalize(ctx.viewVector + L);
|
||||
let NDF = S_DistributionGGX(ctx.fragmentNormal, H, ctx.roughness);
|
||||
let G = S_GeometrySmith(ctx.fragmentNormal, ctx.viewVector, L, ctx.roughness);
|
||||
let F = S_FresnelSchlick(max(dot(H, ctx.viewVector), 0.0), F0);
|
||||
let numerator = NDF * G * F;
|
||||
let denominator = 4.0 * max(dot(ctx.fragmentNormal, ctx.viewVector), 0.0) * max(dot(ctx.fragmentNormal, L), 0.0) + 0.001;
|
||||
let specular = numerator / denominator;
|
||||
let kS = F;
|
||||
var kD = vec3f(1.0) - kS;
|
||||
kD *= 1.0 - ctx.metallic;
|
||||
let NdotL = max(dot(ctx.fragmentNormal, L), 0.0);
|
||||
return (kD * ctx.albedo / PI + specular) * ctx.lightColor * NdotL;
|
||||
}
|
||||
|
||||
fn S_InScatter(start: vec3f, dir: vec3f, lightPos: vec3f, d: f32) -> f32 {
|
||||
let q = start - lightPos;
|
||||
let b = dot(dir, q);
|
||||
let c = dot(q, q);
|
||||
let s = 1.0 / sqrt(c - b * b);
|
||||
let l = s * (atan((d + b) * s) - atan(b * s));
|
||||
return l;
|
||||
}
|
||||
|
||||
fn S_RayleighScatter(start: vec3f, dir: vec3f, lightPos: vec3f, d: f32) -> vec3f {
|
||||
let scatter = S_InScatter(start, dir, lightPos, d);
|
||||
return vec3f(0.55, 0.75, 1.0) * scatter;
|
||||
}
|
||||
|
||||
fn S_RaySphereIntersection(origin: vec3f, dir: vec3f, center: vec3f, radius: f32, minT: ptr<function, f32>, maxT: ptr<function, f32>) -> bool {
|
||||
let d = origin - center;
|
||||
let a = dot(dir, dir);
|
||||
let b = 2.0 * dot(dir, d);
|
||||
let c = dot(d, d) - radius * radius;
|
||||
return S_SolveQuadraticEq(a, b, c, minT, maxT);
|
||||
}
|
||||
|
||||
fn S_PointShadow(
|
||||
shadowsEnabled: bool,
|
||||
softShadows: bool,
|
||||
fragmentDistance: f32,
|
||||
shadowBias: f32,
|
||||
toLight: vec3f,
|
||||
shadowMap_tex: texture_cube<f32>,
|
||||
shadowMap_samp: sampler) -> f32
|
||||
{
|
||||
if (shadowsEnabled) {
|
||||
let biasedFragmentDistance = fragmentDistance - shadowBias;
|
||||
|
||||
if (softShadows) {
|
||||
const directions = array<vec3f, 20>(
|
||||
vec3f(1, 1, 1), vec3f(1, -1, 1), vec3f(-1, -1, 1), vec3f(-1, 1, 1),
|
||||
vec3f(1, 1, -1), vec3f(1, -1, -1), vec3f(-1, -1, -1), vec3f(-1, 1, -1),
|
||||
vec3f(1, 1, 0), vec3f(1, -1, 0), vec3f(-1, -1, 0), vec3f(-1, 1, 0),
|
||||
vec3f(1, 0, 1), vec3f(-1, 0, 1), vec3f(1, 0, -1), vec3f(-1, 0, -1),
|
||||
vec3f(0, 1, 1), vec3f(0, -1, 1), vec3f(0, -1, -1), vec3f(0, 1, -1)
|
||||
);
|
||||
|
||||
const diskRadius = 0.0025;
|
||||
|
||||
var accumulator = 0.0;
|
||||
|
||||
for (var i = 0; i < 20; i++) {
|
||||
let fetchDirection = -toLight + directions[i] * diskRadius;
|
||||
let shadowDistanceToLight = textureSample(shadowMap_tex, shadowMap_samp, fetchDirection).r;
|
||||
if (biasedFragmentDistance > shadowDistanceToLight) {
|
||||
accumulator += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
return clamp(1.0 - accumulator / 20.0, 0.0, 1.0);
|
||||
} else {
|
||||
let shadowDistanceToLight = textureSample(shadowMap_tex, shadowMap_samp, -toLight).r;
|
||||
return select(1.0, 0.0, biasedFragmentDistance > shadowDistanceToLight);
|
||||
}
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
fn S_SpotShadowFactor(
|
||||
shadowsEnabled: bool,
|
||||
softShadows: bool,
|
||||
shadowBias: f32,
|
||||
fragmentPosition: vec3f,
|
||||
lightViewProjMatrix: mat4x4f,
|
||||
shadowMapInvSize: f32,
|
||||
spotShadowTexture_tex: texture_2d<f32>,
|
||||
spotShadowTexture_samp: sampler) -> f32
|
||||
{
|
||||
if (shadowsEnabled) {
|
||||
let lightSpacePosition = S_Project(fragmentPosition, lightViewProjMatrix);
|
||||
|
||||
let biasedLightSpaceFragmentDepth = lightSpacePosition.z - shadowBias;
|
||||
|
||||
if (softShadows) {
|
||||
var accumulator = 0.0;
|
||||
|
||||
let stepSize = 0.5;
|
||||
let kernelHalfSize = 2.0;
|
||||
let kernelSize = 2.0 * kernelHalfSize;
|
||||
let totalSamples = pow(kernelSize / stepSize, 2.0);
|
||||
|
||||
var y = -kernelHalfSize;
|
||||
loop {
|
||||
if (y > kernelHalfSize) { break; }
|
||||
var x = -kernelHalfSize;
|
||||
loop {
|
||||
if (x > kernelHalfSize) { break; }
|
||||
let fetchTexCoord = lightSpacePosition.xy + vec2f(x, y) * shadowMapInvSize;
|
||||
if (biasedLightSpaceFragmentDepth > textureSample(spotShadowTexture_tex, spotShadowTexture_samp, fetchTexCoord).r) {
|
||||
accumulator += 1.0;
|
||||
}
|
||||
x += stepSize;
|
||||
}
|
||||
y += stepSize;
|
||||
}
|
||||
|
||||
return clamp(1.0 - accumulator / totalSamples, 0.0, 1.0);
|
||||
} else {
|
||||
return select(1.0, 0.0, biasedLightSpaceFragmentDepth > textureSample(spotShadowTexture_tex, spotShadowTexture_samp, lightSpacePosition.xy).r);
|
||||
}
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Depth-texture variant of S_PointShadow (textureSample returns f32, no .r needed)
|
||||
fn S_PointShadow_Depth(
|
||||
shadowsEnabled: bool,
|
||||
softShadows: bool,
|
||||
fragmentDistance: f32,
|
||||
shadowBias: f32,
|
||||
toLight: vec3f,
|
||||
shadowMap_tex: texture_depth_cube,
|
||||
shadowMap_samp: sampler) -> f32
|
||||
{
|
||||
if (shadowsEnabled) {
|
||||
let biasedFragmentDistance = fragmentDistance - shadowBias;
|
||||
if (softShadows) {
|
||||
const directions = array<vec3f, 20>(
|
||||
vec3f(1, 1, 1), vec3f(1, -1, 1), vec3f(-1, -1, 1), vec3f(-1, 1, 1),
|
||||
vec3f(1, 1, -1), vec3f(1, -1, -1), vec3f(-1, -1, -1), vec3f(-1, 1, -1),
|
||||
vec3f(1, 1, 0), vec3f(1, -1, 0), vec3f(-1, -1, 0), vec3f(-1, 1, 0),
|
||||
vec3f(1, 0, 1), vec3f(-1, 0, 1), vec3f(1, 0, -1), vec3f(-1, 0, -1),
|
||||
vec3f(0, 1, 1), vec3f(0, -1, 1), vec3f(0, -1, -1), vec3f(0, 1, -1)
|
||||
);
|
||||
const diskRadius = 0.0025;
|
||||
var accumulator = 0.0;
|
||||
for (var i = 0; i < 20; i++) {
|
||||
let fetchDirection = -toLight + directions[i] * diskRadius;
|
||||
let shadowDistanceToLight = textureSample(shadowMap_tex, shadowMap_samp, fetchDirection);
|
||||
if (biasedFragmentDistance > shadowDistanceToLight) { accumulator += 1.0; }
|
||||
}
|
||||
return clamp(1.0 - accumulator / 20.0, 0.0, 1.0);
|
||||
} else {
|
||||
let shadowDistanceToLight = textureSample(shadowMap_tex, shadowMap_samp, -toLight);
|
||||
return select(1.0, 0.0, biasedFragmentDistance > shadowDistanceToLight);
|
||||
}
|
||||
} else { return 1.0; }
|
||||
}
|
||||
|
||||
// Depth-texture variant of S_SpotShadowFactor
|
||||
fn S_SpotShadowFactor_Depth(
|
||||
shadowsEnabled: bool,
|
||||
softShadows: bool,
|
||||
shadowBias: f32,
|
||||
fragmentPosition: vec3f,
|
||||
lightViewProjMatrix: mat4x4f,
|
||||
shadowMapInvSize: f32,
|
||||
spotShadowTexture_tex: texture_depth_2d,
|
||||
spotShadowTexture_samp: sampler) -> f32
|
||||
{
|
||||
if (shadowsEnabled) {
|
||||
let lightSpacePosition = S_Project(fragmentPosition, lightViewProjMatrix);
|
||||
let biasedLightSpaceFragmentDepth = lightSpacePosition.z - shadowBias;
|
||||
if (softShadows) {
|
||||
var accumulator = 0.0;
|
||||
let stepSize = 0.5;
|
||||
let kernelHalfSize = 2.0;
|
||||
let kernelSize = 2.0 * kernelHalfSize;
|
||||
let totalSamples = pow(kernelSize / stepSize, 2.0);
|
||||
var y = -kernelHalfSize;
|
||||
loop {
|
||||
if (y > kernelHalfSize) { break; }
|
||||
var x = -kernelHalfSize;
|
||||
loop {
|
||||
if (x > kernelHalfSize) { break; }
|
||||
let fetchTexCoord = lightSpacePosition.xy + vec2f(x, y) * shadowMapInvSize;
|
||||
if (biasedLightSpaceFragmentDepth > textureSample(spotShadowTexture_tex, spotShadowTexture_samp, fetchTexCoord)) { accumulator += 1.0; }
|
||||
x += stepSize;
|
||||
}
|
||||
y += stepSize;
|
||||
}
|
||||
return clamp(1.0 - accumulator / totalSamples, 0.0, 1.0);
|
||||
} else {
|
||||
return select(1.0, 0.0, biasedLightSpaceFragmentDepth > textureSample(spotShadowTexture_tex, spotShadowTexture_samp, lightSpacePosition.xy));
|
||||
}
|
||||
} else { return 1.0; }
|
||||
}
|
||||
|
||||
fn Internal_FetchHeight(heightTexture_tex: texture_2d<f32>, heightTexture_samp: sampler, texCoords: vec2f, center: f32) -> f32 {
|
||||
return clamp(textureSample(heightTexture_tex, heightTexture_samp, texCoords).r - center, 0.0, 1.0);
|
||||
}
|
||||
|
||||
fn S_ComputeParallaxTextureCoordinates(heightTexture_tex: texture_2d<f32>, heightTexture_samp: sampler, eyeVec: vec3f, texCoords: vec2f, center: f32, scale: f32) -> vec2f {
|
||||
const minLayers = 8.0;
|
||||
const maxLayers = 15.0;
|
||||
const maxIterations = 15;
|
||||
|
||||
let t = max(0.0, abs(dot(vec3f(0.0, 0.0, 1.0), eyeVec)));
|
||||
let numLayers = mix(maxLayers, minLayers, t);
|
||||
let layerDepth = 1.0 / numLayers;
|
||||
var currentLayerDepth = 0.0;
|
||||
|
||||
let deltaTexCoords = scale * eyeVec.xy / numLayers;
|
||||
|
||||
var currentTexCoords = texCoords;
|
||||
var currentDepthMapValue = Internal_FetchHeight(heightTexture_tex, heightTexture_samp, currentTexCoords, center);
|
||||
|
||||
for (var i = 0; i < maxIterations; i++) {
|
||||
if (currentLayerDepth < currentDepthMapValue) {
|
||||
currentTexCoords -= deltaTexCoords;
|
||||
currentDepthMapValue = Internal_FetchHeight(heightTexture_tex, heightTexture_samp, currentTexCoords, center);
|
||||
currentLayerDepth += layerDepth;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let prev = currentTexCoords + deltaTexCoords;
|
||||
let nextH = currentDepthMapValue - currentLayerDepth;
|
||||
let prevH = Internal_FetchHeight(heightTexture_tex, heightTexture_samp, prev, center) - currentLayerDepth + layerDepth;
|
||||
|
||||
let weight = nextH / (nextH - prevH);
|
||||
|
||||
return prev * weight + currentTexCoords * (1.0 - weight);
|
||||
}
|
||||
|
||||
fn S_LinearIndexToPosition(index: i32, textureWidth: i32) -> vec2i {
|
||||
let y = index / textureWidth;
|
||||
let x = index - textureWidth * y;
|
||||
return vec2i(x, y);
|
||||
}
|
||||
|
||||
fn S_FetchMatrix(storage_tex: texture_2d<f32>, storage_samp: sampler, index: i32) -> mat4x4f {
|
||||
let textureWidth = i32(textureDimensions(storage_tex, 0).x);
|
||||
let pos = S_LinearIndexToPosition(4 * index, textureWidth);
|
||||
|
||||
let col1 = textureLoad(storage_tex, pos, 0);
|
||||
let col2 = textureLoad(storage_tex, vec2i(pos.x + 1, pos.y), 0);
|
||||
let col3 = textureLoad(storage_tex, vec2i(pos.x + 2, pos.y), 0);
|
||||
let col4 = textureLoad(storage_tex, vec2i(pos.x + 3, pos.y), 0);
|
||||
|
||||
return mat4x4f(col1, col2, col3, col4);
|
||||
}
|
||||
|
||||
struct TBlendShapeOffsets {
|
||||
position: vec3f,
|
||||
normal: vec3f,
|
||||
tangent: vec3f,
|
||||
};
|
||||
|
||||
fn S_FetchBlendShapeOffsets(storage_tex: texture_3d<f32>, storage_samp: sampler, vertexIndex: i32, blendShapeIndex: i32) -> TBlendShapeOffsets {
|
||||
let textureWidth = i32(textureDimensions(storage_tex, 0).x);
|
||||
let pos = vec3i(S_LinearIndexToPosition(3 * vertexIndex, textureWidth), blendShapeIndex);
|
||||
let position = textureLoad(storage_tex, pos, 0).xyz;
|
||||
let normal = textureLoad(storage_tex, vec3i(pos.x + 1, pos.y, pos.z), 0).xyz;
|
||||
let tangent = textureLoad(storage_tex, vec3i(pos.x + 2, pos.y, pos.z), 0).xyz;
|
||||
return TBlendShapeOffsets(position, normal, tangent);
|
||||
}
|
||||
|
||||
fn S_LinearToSRGB(color: vec4f) -> vec4f {
|
||||
let a = 12.92 * color.rgb;
|
||||
let b = 1.055 * pow(color.rgb, vec3f(1.0 / 2.4)) - 0.055;
|
||||
let c = step(vec3f(0.0031308), color.rgb);
|
||||
return vec4f(mix(a, b, c), color.a);
|
||||
}
|
||||
|
||||
fn S_SRGBToLinear(color: vec4f) -> vec4f {
|
||||
let a = color.rgb / 12.92;
|
||||
let b = pow((color.rgb + 0.055) / 1.055, vec3f(2.4));
|
||||
let c = step(vec3f(0.04045), color.rgb);
|
||||
return vec4f(mix(a, b, c), color.a);
|
||||
}
|
||||
|
||||
fn S_Luminance(x: vec3f) -> f32 {
|
||||
return dot(x, vec3f(0.2125, 0.7154, 0.0721));
|
||||
}
|
||||
|
||||
fn S_RotateVec2(v: vec2f, angle: f32) -> vec2f {
|
||||
let c = cos(angle);
|
||||
let s = sin(angle);
|
||||
let m = mat2x2f(c, -s, s, c);
|
||||
return m * v;
|
||||
}
|
||||
|
||||
fn S_ConvertRgbToXyz(rgb: vec3f) -> vec3f {
|
||||
var xyz: vec3f;
|
||||
xyz.x = dot(vec3f(0.4124564, 0.3575761, 0.1804375), rgb);
|
||||
xyz.y = dot(vec3f(0.2126729, 0.7151522, 0.0721750), rgb);
|
||||
xyz.z = dot(vec3f(0.0193339, 0.1191920, 0.9503041), rgb);
|
||||
return xyz;
|
||||
}
|
||||
|
||||
fn S_ConvertXyzToRgb(xyz: vec3f) -> vec3f {
|
||||
var rgb: vec3f;
|
||||
rgb.x = dot(vec3f(3.2404542, -1.5371385, -0.4985314), xyz);
|
||||
rgb.y = dot(vec3f(-0.9692660, 1.8760108, 0.0415560), xyz);
|
||||
rgb.z = dot(vec3f(0.0556434, -0.2040259, 1.0572252), xyz);
|
||||
return rgb;
|
||||
}
|
||||
|
||||
fn S_ConvertXyzToYxy(xyz: vec3f) -> vec3f {
|
||||
let inv = 1.0 / dot(xyz, vec3f(1.0, 1.0, 1.0));
|
||||
return vec3f(xyz.y, xyz.x * inv, xyz.y * inv);
|
||||
}
|
||||
|
||||
fn S_ConvertYxyToXyz(Yxy: vec3f) -> vec3f {
|
||||
var xyz: vec3f;
|
||||
xyz.x = Yxy.x * Yxy.y / Yxy.z;
|
||||
xyz.y = Yxy.x;
|
||||
xyz.z = Yxy.x * (1.0 - Yxy.y - Yxy.z) / Yxy.z;
|
||||
return xyz;
|
||||
}
|
||||
|
||||
fn S_ConvertRgbToYxy(rgb: vec3f) -> vec3f {
|
||||
return S_ConvertXyzToYxy(S_ConvertRgbToXyz(rgb));
|
||||
}
|
||||
|
||||
fn S_ConvertYxyToRgb(Yxy: vec3f) -> vec3f {
|
||||
return S_ConvertXyzToRgb(S_ConvertYxyToXyz(Yxy));
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use crate::server::WgpuGraphicsServer;
|
||||
use fyrox_graphics::{
|
||||
error::FrameworkError,
|
||||
gpu_texture::{
|
||||
image_1d_size_bytes, image_2d_size_bytes, image_3d_size_bytes, GpuTextureDescriptor,
|
||||
GpuTextureKind, GpuTextureTrait, PixelKind,
|
||||
},
|
||||
};
|
||||
use std::cell::Cell;
|
||||
use std::rc::Weak;
|
||||
|
||||
/// Maps a Fyrox [`PixelKind`] to the corresponding wgpu [`TextureFormat`].
|
||||
///
|
||||
/// 3-component formats (RGB8, BGR8, SRGB8, RGB16F, RGB32F) are mapped to their
|
||||
/// 4-component equivalents because wgpu has no 3-component texture formats.
|
||||
/// The actual data expansion happens in [`expand_to_rgba`].
|
||||
fn pixel_kind_to_wgpu_format(kind: PixelKind) -> wgpu::TextureFormat {
|
||||
match kind {
|
||||
PixelKind::R32F => wgpu::TextureFormat::R32Float,
|
||||
PixelKind::R32UI => wgpu::TextureFormat::R32Uint,
|
||||
PixelKind::R16F => wgpu::TextureFormat::R16Float,
|
||||
PixelKind::RG16F => wgpu::TextureFormat::Rg16Float,
|
||||
PixelKind::D32F => wgpu::TextureFormat::Depth32Float,
|
||||
PixelKind::D16 => wgpu::TextureFormat::Depth16Unorm,
|
||||
PixelKind::D24S8 => wgpu::TextureFormat::Depth24PlusStencil8,
|
||||
PixelKind::RGBA8 | PixelKind::RGB8 => wgpu::TextureFormat::Rgba8Unorm,
|
||||
PixelKind::SRGBA8 | PixelKind::SRGB8 => wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
PixelKind::BGRA8 | PixelKind::BGR8 => wgpu::TextureFormat::Bgra8Unorm,
|
||||
PixelKind::RG8 | PixelKind::LA8 => wgpu::TextureFormat::Rg8Unorm,
|
||||
PixelKind::R8 | PixelKind::L8 => wgpu::TextureFormat::R8Unorm,
|
||||
PixelKind::R8UI => wgpu::TextureFormat::R8Uint,
|
||||
PixelKind::RGBA16F => wgpu::TextureFormat::Rgba16Float,
|
||||
PixelKind::RGB16F => wgpu::TextureFormat::Rgba16Float,
|
||||
PixelKind::RGBA32F | PixelKind::RGB32F => wgpu::TextureFormat::Rgba32Float,
|
||||
PixelKind::R11G11B10F => wgpu::TextureFormat::Rg11b10Ufloat,
|
||||
PixelKind::RGB10A2 => wgpu::TextureFormat::Rgb10a2Unorm,
|
||||
_ => wgpu::TextureFormat::Rgba8Unorm,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the pixel kind is 3-component and needs expansion to 4-component
|
||||
/// for wgpu compatibility (wgpu has no 3-component texture formats).
|
||||
fn needs_rgba_expansion(pk: PixelKind) -> bool {
|
||||
matches!(
|
||||
pk,
|
||||
PixelKind::RGB8
|
||||
| PixelKind::BGR8
|
||||
| PixelKind::SRGB8
|
||||
| PixelKind::RGB16F
|
||||
| PixelKind::RGB32F
|
||||
)
|
||||
}
|
||||
|
||||
/// Expands 3-component pixel data to 4-component by adding an opaque alpha channel.
|
||||
fn expand_to_rgba(pk: PixelKind, data: &[u8]) -> Vec<u8> {
|
||||
match pk {
|
||||
PixelKind::RGB8 | PixelKind::SRGB8 | PixelKind::BGR8 => {
|
||||
// 3 bytes → 4 bytes per pixel (add 0xFF alpha)
|
||||
let pixel_count = data.len() / 3;
|
||||
let mut out = Vec::with_capacity(pixel_count * 4);
|
||||
for chunk in data.chunks(3) {
|
||||
out.extend_from_slice(chunk);
|
||||
out.push(0xFF);
|
||||
}
|
||||
out
|
||||
}
|
||||
PixelKind::RGB16F => {
|
||||
// 6 bytes → 8 bytes per pixel (add 1.0f16 = 0x3C00 as alpha)
|
||||
let pixel_count = data.len() / 6;
|
||||
let mut out = Vec::with_capacity(pixel_count * 8);
|
||||
for chunk in data.chunks(6) {
|
||||
out.extend_from_slice(chunk);
|
||||
out.extend_from_slice(&[0x00, 0x3C]);
|
||||
}
|
||||
out
|
||||
}
|
||||
PixelKind::RGB32F => {
|
||||
// 12 bytes → 16 bytes per pixel (add 1.0f32 = 0x3F800000 as alpha)
|
||||
let pixel_count = data.len() / 12;
|
||||
let mut out = Vec::with_capacity(pixel_count * 16);
|
||||
for chunk in data.chunks(12) {
|
||||
out.extend_from_slice(chunk);
|
||||
out.extend_from_slice(&[0x00, 0x00, 0x80, 0x3F]);
|
||||
}
|
||||
out
|
||||
}
|
||||
_ => data.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
fn texture_dimension(kind: GpuTextureKind) -> wgpu::TextureDimension {
|
||||
match kind {
|
||||
GpuTextureKind::Line { .. } => wgpu::TextureDimension::D1,
|
||||
GpuTextureKind::Rectangle { .. } | GpuTextureKind::Cube { .. } => {
|
||||
wgpu::TextureDimension::D2
|
||||
}
|
||||
GpuTextureKind::Volume { .. } => wgpu::TextureDimension::D3,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn texture_size(kind: GpuTextureKind) -> (u32, u32, u32) {
|
||||
match kind {
|
||||
GpuTextureKind::Line { length } => (length as u32, 1, 1),
|
||||
GpuTextureKind::Rectangle { width, height } => (width as u32, height as u32, 1),
|
||||
GpuTextureKind::Cube { size } => (size as u32, size as u32, 6),
|
||||
GpuTextureKind::Volume {
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
} => (width as u32, height as u32, depth as u32),
|
||||
}
|
||||
}
|
||||
|
||||
fn texture_view_dimension(kind: GpuTextureKind) -> wgpu::TextureViewDimension {
|
||||
match kind {
|
||||
GpuTextureKind::Line { .. } => wgpu::TextureViewDimension::D1,
|
||||
GpuTextureKind::Rectangle { .. } => wgpu::TextureViewDimension::D2,
|
||||
GpuTextureKind::Cube { .. } => wgpu::TextureViewDimension::Cube,
|
||||
GpuTextureKind::Volume { .. } => wgpu::TextureViewDimension::D3,
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a single mip level (or cubemap face) to a texture, expanding 3-component
|
||||
/// pixel data to 4-component if needed for wgpu compatibility.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn write_mip_data(
|
||||
queue: &wgpu::Queue,
|
||||
texture: &wgpu::Texture,
|
||||
mip: u32,
|
||||
origin: wgpu::Origin3d,
|
||||
extent: wgpu::Extent3d,
|
||||
pk: PixelKind,
|
||||
data: &[u8],
|
||||
fmt: wgpu::TextureFormat,
|
||||
) {
|
||||
let expanded;
|
||||
let upload_data = if needs_rgba_expansion(pk) {
|
||||
expanded = expand_to_rgba(pk, data);
|
||||
expanded.as_slice()
|
||||
} else {
|
||||
data
|
||||
};
|
||||
let bps = fmt.block_copy_size(None).unwrap_or(4);
|
||||
queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture,
|
||||
mip_level: mip,
|
||||
origin,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
upload_data,
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some((extent.width * bps).max(1)),
|
||||
rows_per_image: Some(extent.height.max(1)),
|
||||
},
|
||||
extent,
|
||||
);
|
||||
}
|
||||
|
||||
/// Wgpu implementation of [`GpuTextureTrait`].
|
||||
///
|
||||
/// Wraps a [`wgpu::Texture`] with two views: a full-aspect view for rendering and
|
||||
/// a separate `DepthOnly` view for shader bindings (depth-stencil textures require
|
||||
/// this because wgpu rejects `Depth+Stencil` aspect on texture bindings).
|
||||
///
|
||||
/// Memory usage is tracked on the server and decremented on drop.
|
||||
pub struct WgpuTexture {
|
||||
server: Weak<WgpuGraphicsServer>,
|
||||
texture: wgpu::Texture,
|
||||
view: wgpu::TextureView,
|
||||
/// Separate view with DepthOnly aspect for shader bindings of depth-stencil textures.
|
||||
/// For non-depth-stencil textures, this is the same as `view`.
|
||||
binding_view: wgpu::TextureView,
|
||||
kind: Cell<GpuTextureKind>,
|
||||
pixel_kind: Cell<PixelKind>,
|
||||
size_bytes: Cell<usize>,
|
||||
}
|
||||
|
||||
impl WgpuTexture {
|
||||
/// Creates a new GPU texture from the given descriptor.
|
||||
///
|
||||
/// Creates the wgpu texture, views (including a separate `DepthOnly` binding
|
||||
/// view for depth-stencil formats), uploads initial data if provided, and
|
||||
/// tracks memory usage on the server.
|
||||
pub fn new(
|
||||
server: &WgpuGraphicsServer,
|
||||
desc: GpuTextureDescriptor,
|
||||
) -> Result<Self, FrameworkError> {
|
||||
let format = pixel_kind_to_wgpu_format(desc.pixel_kind);
|
||||
let dimension = texture_dimension(desc.kind);
|
||||
let (raw_w, raw_h, depth_or_layers) = texture_size(desc.kind);
|
||||
let width = raw_w.max(1);
|
||||
let height = raw_h.max(1);
|
||||
let mip_count = desc.mip_count.max(1) as u32;
|
||||
|
||||
let texture = server
|
||||
.state
|
||||
.device
|
||||
.create_texture(&wgpu::TextureDescriptor {
|
||||
label: if server.named_objects {
|
||||
Some(desc.name)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: depth_or_layers,
|
||||
},
|
||||
mip_level_count: mip_count,
|
||||
sample_count: 1,
|
||||
dimension,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING
|
||||
| wgpu::TextureUsages::COPY_DST
|
||||
| wgpu::TextureUsages::COPY_SRC
|
||||
| wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
label: None,
|
||||
format: None,
|
||||
dimension: Some(texture_view_dimension(desc.kind)),
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// For depth-stencil textures, create a separate view with DepthOnly aspect
|
||||
// for shader bindings. wgpu rejects Depth+Stencil aspect on texture bindings.
|
||||
let binding_view = match format {
|
||||
wgpu::TextureFormat::Depth24PlusStencil8
|
||||
| wgpu::TextureFormat::Depth32FloatStencil8 => {
|
||||
texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
label: None,
|
||||
format: None,
|
||||
dimension: Some(texture_view_dimension(desc.kind)),
|
||||
aspect: wgpu::TextureAspect::DepthOnly,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
_ => view.clone(),
|
||||
};
|
||||
|
||||
let size_bytes = calc_size_bytes(desc.kind, desc.pixel_kind, desc.mip_count);
|
||||
if let Some(data) = desc.data {
|
||||
Self::upload(
|
||||
&server.state.queue,
|
||||
&texture,
|
||||
desc.kind,
|
||||
desc.pixel_kind,
|
||||
data,
|
||||
desc.mip_count,
|
||||
)?;
|
||||
}
|
||||
server.memory_usage.borrow_mut().textures += size_bytes;
|
||||
|
||||
Ok(Self {
|
||||
server: server.weak_ref(),
|
||||
texture,
|
||||
view,
|
||||
binding_view,
|
||||
kind: Cell::new(desc.kind),
|
||||
pixel_kind: Cell::new(desc.pixel_kind),
|
||||
size_bytes: Cell::new(size_bytes),
|
||||
})
|
||||
}
|
||||
|
||||
fn upload(
|
||||
queue: &wgpu::Queue,
|
||||
texture: &wgpu::Texture,
|
||||
kind: GpuTextureKind,
|
||||
pk: PixelKind,
|
||||
data: &[u8],
|
||||
mip_count: usize,
|
||||
) -> Result<(), FrameworkError> {
|
||||
let mip_count = mip_count.max(1);
|
||||
let fmt = pixel_kind_to_wgpu_format(pk);
|
||||
let mut offset = 0;
|
||||
for mip in 0..mip_count {
|
||||
match kind {
|
||||
GpuTextureKind::Line { length } => {
|
||||
if let Some(l) = length.checked_shr(mip as u32) {
|
||||
let sz = image_1d_size_bytes(pk, l);
|
||||
if offset + sz > data.len() {
|
||||
break;
|
||||
}
|
||||
write_mip_data(
|
||||
queue,
|
||||
texture,
|
||||
mip as u32,
|
||||
wgpu::Origin3d::ZERO,
|
||||
wgpu::Extent3d {
|
||||
width: l as u32,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
pk,
|
||||
&data[offset..offset + sz],
|
||||
fmt,
|
||||
);
|
||||
offset += sz;
|
||||
}
|
||||
}
|
||||
GpuTextureKind::Rectangle { width, height } => {
|
||||
if let (Some(w), Some(h)) = (
|
||||
width.checked_shr(mip as u32),
|
||||
height.checked_shr(mip as u32),
|
||||
) {
|
||||
let sz = image_2d_size_bytes(pk, w, h);
|
||||
if offset + sz > data.len() {
|
||||
break;
|
||||
}
|
||||
write_mip_data(
|
||||
queue,
|
||||
texture,
|
||||
mip as u32,
|
||||
wgpu::Origin3d::ZERO,
|
||||
wgpu::Extent3d {
|
||||
width: w as u32,
|
||||
height: h as u32,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
pk,
|
||||
&data[offset..offset + sz],
|
||||
fmt,
|
||||
);
|
||||
offset += sz;
|
||||
}
|
||||
}
|
||||
GpuTextureKind::Cube { size } => {
|
||||
if let Some(s) = size.checked_shr(mip as u32) {
|
||||
let bpf = image_2d_size_bytes(pk, s, s);
|
||||
for face in 0..6u32 {
|
||||
let fo = offset + (face as usize) * bpf;
|
||||
if fo + bpf > data.len() {
|
||||
break;
|
||||
}
|
||||
write_mip_data(
|
||||
queue,
|
||||
texture,
|
||||
mip as u32,
|
||||
wgpu::Origin3d {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: face,
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: s as u32,
|
||||
height: s as u32,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
pk,
|
||||
&data[fo..fo + bpf],
|
||||
fmt,
|
||||
);
|
||||
}
|
||||
offset += 6 * bpf;
|
||||
}
|
||||
}
|
||||
GpuTextureKind::Volume {
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
} => {
|
||||
if let (Some(w), Some(h), Some(d)) = (
|
||||
width.checked_shr(mip as u32),
|
||||
height.checked_shr(mip as u32),
|
||||
depth.checked_shr(mip as u32),
|
||||
) {
|
||||
let sz = image_3d_size_bytes(pk, w, h, d);
|
||||
if offset + sz > data.len() {
|
||||
break;
|
||||
}
|
||||
write_mip_data(
|
||||
queue,
|
||||
texture,
|
||||
mip as u32,
|
||||
wgpu::Origin3d::ZERO,
|
||||
wgpu::Extent3d {
|
||||
width: w as u32,
|
||||
height: h as u32,
|
||||
depth_or_array_layers: d as u32,
|
||||
},
|
||||
pk,
|
||||
&data[offset..offset + sz],
|
||||
fmt,
|
||||
);
|
||||
offset += sz;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying [`wgpu::Texture`].
|
||||
pub fn wgpu_texture(&self) -> &wgpu::Texture {
|
||||
&self.texture
|
||||
}
|
||||
/// Returns a reference to the full-aspect [`wgpu::TextureView`].
|
||||
pub fn wgpu_view(&self) -> &wgpu::TextureView {
|
||||
&self.view
|
||||
}
|
||||
/// Returns a view suitable for shader bindings.
|
||||
///
|
||||
/// For depth-stencil textures, this is a `DepthOnly`-aspect view (wgpu rejects
|
||||
/// `Depth+Stencil` aspect on texture bindings). For all other textures, this
|
||||
/// is the same as [`wgpu_view`](Self::wgpu_view).
|
||||
pub fn wgpu_binding_view(&self) -> &wgpu::TextureView {
|
||||
&self.binding_view
|
||||
}
|
||||
/// Returns the wgpu texture format of this texture.
|
||||
pub fn format(&self) -> wgpu::TextureFormat {
|
||||
self.texture.format()
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_size_bytes(kind: GpuTextureKind, pk: PixelKind, mip_count: usize) -> usize {
|
||||
let mip_count = mip_count.max(1);
|
||||
let mut total = 0;
|
||||
for mip in 0..mip_count {
|
||||
match kind {
|
||||
GpuTextureKind::Line { length } => {
|
||||
if let Some(l) = length.checked_shr(mip as u32) {
|
||||
total += image_1d_size_bytes(pk, l);
|
||||
}
|
||||
}
|
||||
GpuTextureKind::Rectangle { width, height } => {
|
||||
if let (Some(w), Some(h)) = (
|
||||
width.checked_shr(mip as u32),
|
||||
height.checked_shr(mip as u32),
|
||||
) {
|
||||
total += image_2d_size_bytes(pk, w, h);
|
||||
}
|
||||
}
|
||||
GpuTextureKind::Cube { size } => {
|
||||
if let Some(s) = size.checked_shr(mip as u32) {
|
||||
total += 6 * image_2d_size_bytes(pk, s, s);
|
||||
}
|
||||
}
|
||||
GpuTextureKind::Volume {
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
} => {
|
||||
if let (Some(w), Some(h), Some(d)) = (
|
||||
width.checked_shr(mip as u32),
|
||||
height.checked_shr(mip as u32),
|
||||
depth.checked_shr(mip as u32),
|
||||
) {
|
||||
total += image_3d_size_bytes(pk, w, h, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
total
|
||||
}
|
||||
|
||||
impl Drop for WgpuTexture {
|
||||
fn drop(&mut self) {
|
||||
if let Some(server) = self.server.upgrade() {
|
||||
server.memory_usage.borrow_mut().textures -= self.size_bytes.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuTextureTrait for WgpuTexture {
|
||||
fn set_data(
|
||||
&self,
|
||||
kind: GpuTextureKind,
|
||||
pk: PixelKind,
|
||||
mip_count: usize,
|
||||
data: Option<&[u8]>,
|
||||
) -> Result<usize, FrameworkError> {
|
||||
let Some(server) = self.server.upgrade() else {
|
||||
return Err(FrameworkError::GraphicsServerUnavailable);
|
||||
};
|
||||
let new_size = calc_size_bytes(kind, pk, mip_count);
|
||||
let mut mu = server.memory_usage.borrow_mut();
|
||||
mu.textures -= self.size_bytes.get();
|
||||
mu.textures += new_size;
|
||||
drop(mu);
|
||||
self.size_bytes.set(new_size);
|
||||
self.kind.set(kind);
|
||||
self.pixel_kind.set(pk);
|
||||
if let Some(data) = data {
|
||||
Self::upload(
|
||||
&server.state.queue,
|
||||
&self.texture,
|
||||
kind,
|
||||
pk,
|
||||
data,
|
||||
mip_count,
|
||||
)?;
|
||||
}
|
||||
Ok(new_size)
|
||||
}
|
||||
fn kind(&self) -> GpuTextureKind {
|
||||
self.kind.get()
|
||||
}
|
||||
fn pixel_kind(&self) -> PixelKind {
|
||||
self.pixel_kind.get()
|
||||
}
|
||||
}
|
||||
@@ -153,6 +153,10 @@ pub enum SamplerKind {
|
||||
/// The sampler follows the direction of the coordinates until it finds a place on one of the six faces of the cube.
|
||||
/// Each component of the resulting value is an unsigned integer.
|
||||
USamplerCube,
|
||||
/// A sampler for a 2D depth texture.
|
||||
DepthSampler2D,
|
||||
/// A sampler for a cube depth texture.
|
||||
DepthSamplerCube,
|
||||
}
|
||||
|
||||
/// Shader property with default value.
|
||||
|
||||
@@ -43,8 +43,10 @@ use std::rc::{Rc, Weak};
|
||||
/// Graphics server capabilities.
|
||||
#[derive(Debug)]
|
||||
pub struct ServerCapabilities {
|
||||
/// The maximum size in basic machine units of a uniform block, which must be at least 16384.
|
||||
pub max_uniform_block_size: usize,
|
||||
/// The maximum size in bytes of a single uniform buffer binding in a bind group.
|
||||
/// This corresponds to `GL_MAX_UNIFORM_BLOCK_SIZE` (OpenGL) and
|
||||
/// `max_uniform_buffer_binding_size` (wgpu). Must be at least 16384.
|
||||
pub max_uniform_buffer_binding_size: usize,
|
||||
/// The minimum required alignment for uniform buffer sizes and offset. The initial value is 1.
|
||||
pub uniform_buffer_offset_alignment: usize,
|
||||
/// The maximum, absolute value of the texture level-of-detail bias. The value must be at least
|
||||
|
||||
@@ -23,10 +23,11 @@ fyrox-resource = { path = "../fyrox-resource", version = "2.0.0-rc.1" }
|
||||
fyrox-animation = { path = "../fyrox-animation", version = "2.0.0-rc.1" }
|
||||
fyrox-graph = { path = "../fyrox-graph", version = "2.0.0-rc.1" }
|
||||
fyrox-graphics = { path = "../fyrox-graphics", version = "2.0.0-rc.1" }
|
||||
fyrox-graphics-gl = { path = "../fyrox-graphics-gl", version = "2.0.0-rc.1" }
|
||||
fyrox-texture = { path = "../fyrox-texture", version = "2.0.0-rc.1" }
|
||||
fyrox-autotile = { path = "../fyrox-autotile", version = "2.0.0-rc.1" }
|
||||
fyrox-material = { path = "../fyrox-material", version = "2.0.0-rc.1" }
|
||||
fyrox-graphics-gl = { path = "../fyrox-graphics-gl", version = "2.0.0-rc.1", optional = true }
|
||||
fyrox-graphics-wgpu = { path = "../fyrox-graphics-wgpu", version = "2.0.0-rc.1", optional = true }
|
||||
rapier2d = { version = "0.33", features = ["debug-render", ] }
|
||||
rapier3d = { version = "0.33", features = ["debug-render"] }
|
||||
image = { version = "0.25.1", default-features = false, features = ["gif", "jpeg", "png", "tga", "tiff", "bmp"] }
|
||||
@@ -93,6 +94,8 @@ opener = { version = "0.8", default-features = false, features = ["reveal"] }
|
||||
# === end of additional deps ===
|
||||
|
||||
[features]
|
||||
backend_opengl = ["dep:fyrox-graphics-gl", "fyrox-material/backend_opengl"]
|
||||
backend_wgpu = ["dep:fyrox-graphics-wgpu", "fyrox-material/backend_wgpu"]
|
||||
mesh_analysis = []
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
|
||||
@@ -113,7 +113,7 @@ use fyrox_animation::AnimationTracksData;
|
||||
use fyrox_core::dyntype::DynTypeConstructorContainer;
|
||||
use fyrox_core::NameProvider;
|
||||
use fyrox_graphics::server::SharedGraphicsServer;
|
||||
use fyrox_graphics_gl::server::GlGraphicsServer;
|
||||
|
||||
use fyrox_sound::{
|
||||
buffer::{loader::SoundBufferLoader, SoundBuffer},
|
||||
renderer::hrtf::{HrirSphereLoader, HrirSphereResourceData},
|
||||
@@ -1022,11 +1022,29 @@ pub type GraphicsServerConstructorCallback = dyn Fn(
|
||||
#[derive(Clone)]
|
||||
pub struct GraphicsServerConstructor(Rc<GraphicsServerConstructorCallback>);
|
||||
|
||||
#[cfg(feature = "backend_opengl")]
|
||||
impl Default for GraphicsServerConstructor {
|
||||
fn default() -> Self {
|
||||
Self(Rc::new(
|
||||
|params, window_target, window_builder, named_objects| {
|
||||
GlGraphicsServer::new(
|
||||
fyrox_graphics_gl::server::GlGraphicsServer::new(
|
||||
params.vsync,
|
||||
params.msaa_sample_count,
|
||||
window_target,
|
||||
window_builder,
|
||||
named_objects,
|
||||
)
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "backend_opengl"))]
|
||||
impl Default for GraphicsServerConstructor {
|
||||
fn default() -> Self {
|
||||
Self(Rc::new(
|
||||
|params, window_target, window_builder, named_objects| {
|
||||
fyrox_graphics_wgpu::server::WgpuGraphicsServer::new(
|
||||
params.vsync,
|
||||
params.msaa_sample_count,
|
||||
window_target,
|
||||
|
||||
@@ -69,9 +69,6 @@ pub use fyrox_autotile as autotile;
|
||||
#[doc(inline)]
|
||||
pub use fyrox_graphics as graphics;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use fyrox_graphics_gl as graphics_gl;
|
||||
|
||||
/// Defines a builder's `with_xxx` method.
|
||||
#[macro_export]
|
||||
macro_rules! define_with {
|
||||
|
||||
+20
-9
@@ -88,7 +88,15 @@ impl UniformBufferCache {
|
||||
pub fn get_or_create(&self, size: usize) -> Result<GpuBuffer, FrameworkError> {
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
let set = cache.entry(size).or_default();
|
||||
set.get_or_create(size, &*self.server)
|
||||
let buffer = set.get_or_create(size, &*self.server)?;
|
||||
debug_assert_eq!(
|
||||
buffer.size(),
|
||||
size,
|
||||
"UniformBufferCache returned buffer of size {} but {} was requested",
|
||||
buffer.size(),
|
||||
size
|
||||
);
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Fetches a suitable (or creates new one) GPU uniform buffer for the given CPU uniform buffer
|
||||
@@ -140,17 +148,20 @@ pub struct UniformBlockLocation {
|
||||
pub struct UniformMemoryAllocator {
|
||||
gpu_buffers: Vec<GpuBuffer>,
|
||||
block_alignment: usize,
|
||||
max_uniform_buffer_size: usize,
|
||||
/// Maximum size of a single uniform buffer binding. This is the GPU's
|
||||
/// `max_uniform_buffer_binding_size` and is used as both the page capacity
|
||||
/// and the per-allocation size limit.
|
||||
max_uniform_buffer_binding_size: usize,
|
||||
pages: Vec<Page>,
|
||||
blocks: Vec<UniformBlockLocation>,
|
||||
}
|
||||
|
||||
impl UniformMemoryAllocator {
|
||||
pub fn new(max_uniform_buffer_size: usize, block_alignment: usize) -> Self {
|
||||
pub fn new(max_uniform_buffer_binding_size: usize, block_alignment: usize) -> Self {
|
||||
Self {
|
||||
gpu_buffers: Default::default(),
|
||||
block_alignment,
|
||||
max_uniform_buffer_size,
|
||||
max_uniform_buffer_binding_size,
|
||||
pages: Default::default(),
|
||||
blocks: Default::default(),
|
||||
}
|
||||
@@ -170,20 +181,20 @@ impl UniformMemoryAllocator {
|
||||
{
|
||||
let data = buffer.finish();
|
||||
assert!(data.bytes_count() > 0);
|
||||
assert!(data.bytes_count() < self.max_uniform_buffer_size);
|
||||
assert!(data.bytes_count() < self.max_uniform_buffer_binding_size);
|
||||
|
||||
let page_index = match self.pages.iter().position(|page| {
|
||||
let write_position = page
|
||||
.dynamic
|
||||
.next_write_aligned_position(self.block_alignment);
|
||||
self.max_uniform_buffer_size - write_position >= data.bytes_count()
|
||||
self.max_uniform_buffer_binding_size - write_position >= data.bytes_count()
|
||||
}) {
|
||||
Some(page_index) => page_index,
|
||||
None => {
|
||||
let page_index = self.pages.len();
|
||||
self.pages.push(Page {
|
||||
dynamic: UniformBuffer::with_storage(Vec::with_capacity(
|
||||
self.max_uniform_buffer_size,
|
||||
self.max_uniform_buffer_binding_size,
|
||||
)),
|
||||
is_submitted: false,
|
||||
});
|
||||
@@ -211,7 +222,7 @@ impl UniformMemoryAllocator {
|
||||
for _ in 0..(self.pages.len() - self.gpu_buffers.len()) {
|
||||
let buffer = server.create_buffer(GpuBufferDescriptor {
|
||||
name: &format!("UniformMemoryPage{}", self.gpu_buffers.len()),
|
||||
size: self.max_uniform_buffer_size,
|
||||
size: self.max_uniform_buffer_binding_size,
|
||||
kind: BufferKind::Uniform,
|
||||
usage: BufferUsage::StreamCopy,
|
||||
})?;
|
||||
@@ -222,7 +233,7 @@ impl UniformMemoryAllocator {
|
||||
for (page, gpu_buffer) in self.pages.iter_mut().zip(self.gpu_buffers.iter()) {
|
||||
if !page.is_submitted {
|
||||
let bytes = page.dynamic.storage().bytes();
|
||||
assert!(bytes.len() <= self.max_uniform_buffer_size);
|
||||
assert!(bytes.len() <= self.max_uniform_buffer_binding_size);
|
||||
gpu_buffer.write_data(bytes)?;
|
||||
page.is_submitted = true;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use crate::renderer::make_deferred_viewport_matrix;
|
||||
use crate::{
|
||||
asset::manager::ResourceManager,
|
||||
core::{
|
||||
@@ -48,7 +49,6 @@ use crate::{
|
||||
framework::GeometryBufferExt,
|
||||
gbuffer::GBuffer,
|
||||
light_volume::LightVolumeRenderer,
|
||||
make_viewport_matrix,
|
||||
observer::Observer,
|
||||
resources::RendererResources,
|
||||
shadow::{
|
||||
@@ -291,7 +291,8 @@ impl DeferredLightRenderer {
|
||||
Frustum::from_view_projection_matrix(observer.position.view_projection_matrix)
|
||||
.unwrap_or_default();
|
||||
|
||||
let frame_matrix = make_viewport_matrix(viewport);
|
||||
// TODO: A temporary fix until we figure out what's going on. For some reason, the deferred light renderer requires an inverted viewport matrix.
|
||||
let frame_matrix = make_deferred_viewport_matrix(viewport);
|
||||
|
||||
let inv_projection = observer
|
||||
.position
|
||||
|
||||
@@ -168,16 +168,21 @@ fn recreate_render_data_if_needed<T: Any>(
|
||||
frame_size: Vector2<f32>,
|
||||
final_frame_texture: FrameTextureKind,
|
||||
) -> Result<(), FrameworkError> {
|
||||
if data.gbuffer.width != frame_size.x as i32 || data.gbuffer.height != frame_size.y as i32 {
|
||||
let new_w = frame_size.x as i32;
|
||||
let new_h = frame_size.y as i32;
|
||||
// Use >1px tolerance to prevent oscillation from fractional DPI scaling.
|
||||
if (data.gbuffer.width - new_w).unsigned_abs() > 1
|
||||
|| (data.gbuffer.height - new_h).unsigned_abs() > 1
|
||||
{
|
||||
Log::info(format!(
|
||||
"Associated scene rendering data was re-created for {} ({}), because render \
|
||||
frame size was changed. Old is {}x{}, new {}x{}!",
|
||||
frame size was changed. Old is {}x{}, new is {}x{}!",
|
||||
parent,
|
||||
std::any::type_name::<T>(),
|
||||
data.gbuffer.width,
|
||||
data.gbuffer.height,
|
||||
frame_size.x,
|
||||
frame_size.y
|
||||
new_w,
|
||||
new_h
|
||||
));
|
||||
|
||||
*data = RenderDataContainer::new(server, frame_size, final_frame_texture)?;
|
||||
@@ -358,7 +363,8 @@ impl RenderDataContainer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a view-projection matrix that projects unit quad a screen with the specified viewport.
|
||||
/// Creates a view-projection matrix that projects unit quad to screen with the specified viewport.
|
||||
/// Uses OpenGL-style orthographic parameters (bottom=h, top=0).
|
||||
pub fn make_viewport_matrix(viewport: Rect<i32>) -> Matrix4<f32> {
|
||||
Matrix4::new_orthographic(
|
||||
0.0,
|
||||
@@ -374,6 +380,36 @@ pub fn make_viewport_matrix(viewport: Rect<i32>) -> Matrix4<f32> {
|
||||
))
|
||||
}
|
||||
|
||||
/// Viewport matrix for deferred passes that reconstruct world positions from G-Buffer depth.
|
||||
///
|
||||
/// In wgpu, the viewport Y-axis is flipped relative to OpenGL (Y=0 at top vs bottom).
|
||||
/// Deferred passes that use `S_UnProject` with depth need the orthographic bottom/top
|
||||
/// swapped so that texture coordinates match the G-Buffer's pixel layout.
|
||||
///
|
||||
/// Non-deferred passes (bloom, FXAA, HDR) should use [`make_viewport_matrix`] instead.
|
||||
#[cfg(not(feature = "backend_opengl"))]
|
||||
pub fn make_deferred_viewport_matrix(viewport: Rect<i32>) -> Matrix4<f32> {
|
||||
Matrix4::new_orthographic(
|
||||
0.0,
|
||||
viewport.w() as f32,
|
||||
0.0,
|
||||
viewport.h() as f32,
|
||||
-1.0,
|
||||
1.0,
|
||||
) * Matrix4::new_nonuniform_scaling(&Vector3::new(
|
||||
viewport.w() as f32,
|
||||
viewport.h() as f32,
|
||||
0.0,
|
||||
))
|
||||
}
|
||||
|
||||
/// Viewport matrix for deferred passes that reconstruct world positions from G-Buffer depth.
|
||||
/// On OpenGL this is identical to [`make_viewport_matrix`].
|
||||
#[cfg(feature = "backend_opengl")]
|
||||
pub fn make_deferred_viewport_matrix(viewport: Rect<i32>) -> Matrix4<f32> {
|
||||
make_viewport_matrix(viewport)
|
||||
}
|
||||
|
||||
/// See module docs.
|
||||
pub struct Renderer {
|
||||
backbuffer: GpuFrameBuffer,
|
||||
@@ -639,12 +675,11 @@ impl Renderer {
|
||||
|
||||
let shader_cache = ShaderCache::default();
|
||||
|
||||
let one_megabyte = 1024 * 1024;
|
||||
let uniform_memory_allocator = UniformMemoryAllocator::new(
|
||||
// Clamp max uniform block size from the upper bound, to prevent allocating huge
|
||||
// uniform buffers when GPU supports it. Some AMD GPUs are able to allocate ~500 Mb
|
||||
// uniform buffers, which will lead to ridiculous VRAM consumption.
|
||||
caps.max_uniform_block_size.min(one_megabyte),
|
||||
// Use the GPU's max uniform buffer binding size as the page size limit.
|
||||
// This ensures individual bindings never exceed the limit enforced by
|
||||
// create_bind_group(). On most GPUs this is 64KB.
|
||||
caps.max_uniform_buffer_binding_size,
|
||||
caps.uniform_buffer_offset_alignment,
|
||||
);
|
||||
|
||||
@@ -1246,6 +1281,12 @@ impl Renderer {
|
||||
dt: f32,
|
||||
resource_manager: &ResourceManager,
|
||||
) -> Result<&SceneRenderData, FrameworkError> {
|
||||
if scene_handle.is_none() {
|
||||
return Err(FrameworkError::Custom(
|
||||
"Attempted to render scene with invalid handle".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let graph = &scene.graph;
|
||||
|
||||
let _debug_scope = self.server.begin_scope(&format!("Scene {:p}", scene));
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
//! A set of textures of certain kinds. See [`RendererResources`] docs for more info.
|
||||
|
||||
use crate::{
|
||||
core::{algebra::Matrix4, array_as_u8_slice},
|
||||
core::algebra::Matrix4,
|
||||
graphics::{
|
||||
buffer::GpuBufferDescriptor,
|
||||
buffer::{BufferKind, BufferUsage, GpuBuffer},
|
||||
@@ -98,79 +98,93 @@ pub struct ShadersContainer {
|
||||
pub environment_map_irradiance_convolution: RenderPassContainer,
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_opengl")]
|
||||
macro_rules! include_shader {
|
||||
($file:literal) => {
|
||||
include_str!(concat!("shaders/opengl/", $file))
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "backend_opengl"))]
|
||||
macro_rules! include_shader {
|
||||
($file:literal) => {
|
||||
include_str!(concat!("shaders/wgpu/", $file))
|
||||
};
|
||||
}
|
||||
|
||||
impl ShadersContainer {
|
||||
/// Creates a new shaders container.
|
||||
/// Creates a new shaders' container.
|
||||
pub fn new(server: &dyn GraphicsServer) -> Result<Self, FrameworkError> {
|
||||
Ok(Self {
|
||||
decal: RenderPassContainer::from_str(server, include_str!("shaders/decal.shader"))?,
|
||||
decal: RenderPassContainer::from_str(server, include_shader!("decal.shader"))?,
|
||||
spot_light: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/deferred_spot_light.shader"),
|
||||
include_shader!("deferred_spot_light.shader"),
|
||||
)?,
|
||||
point_light: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/deferred_point_light.shader"),
|
||||
include_shader!("deferred_point_light.shader"),
|
||||
)?,
|
||||
directional_light: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/deferred_directional_light.shader"),
|
||||
include_shader!("deferred_directional_light.shader"),
|
||||
)?,
|
||||
ambient_light: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/ambient_light.shader"),
|
||||
include_shader!("ambient_light.shader"),
|
||||
)?,
|
||||
volume_marker_lit: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/volume_marker_lit.shader"),
|
||||
include_shader!("volume_marker_lit.shader"),
|
||||
)?,
|
||||
pixel_counter: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/pixel_counter.shader"),
|
||||
include_shader!("pixel_counter.shader"),
|
||||
)?,
|
||||
debug: RenderPassContainer::from_str(server, include_str!("shaders/debug.shader"))?,
|
||||
fxaa: RenderPassContainer::from_str(server, include_str!("shaders/fxaa.shader"))?,
|
||||
debug: RenderPassContainer::from_str(server, include_shader!("debug.shader"))?,
|
||||
fxaa: RenderPassContainer::from_str(server, include_shader!("fxaa.shader"))?,
|
||||
spot_light_volume: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/spot_volumetric.shader"),
|
||||
include_shader!("spot_volumetric.shader"),
|
||||
)?,
|
||||
point_light_volume: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/point_volumetric.shader"),
|
||||
include_shader!("point_volumetric.shader"),
|
||||
)?,
|
||||
volume_marker_vol: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/volume_marker_vol.shader"),
|
||||
include_shader!("volume_marker_vol.shader"),
|
||||
)?,
|
||||
visibility_optimizer: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/visibility_optimizer.shader"),
|
||||
include_shader!("visibility_optimizer.shader"),
|
||||
)?,
|
||||
ssao: RenderPassContainer::from_str(server, include_str!("shaders/ssao.shader"))?,
|
||||
ssao: RenderPassContainer::from_str(server, include_shader!("ssao.shader"))?,
|
||||
visibility: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/visibility.shader"),
|
||||
include_shader!("visibility.shader"),
|
||||
)?,
|
||||
blit: RenderPassContainer::from_str(server, include_str!("shaders/blit.shader"))?,
|
||||
blit: RenderPassContainer::from_str(server, include_shader!("blit.shader"))?,
|
||||
hdr_adaptation: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/hdr_adaptation.shader"),
|
||||
include_shader!("hdr_adaptation.shader"),
|
||||
)?,
|
||||
hdr_luminance: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/hdr_luminance.shader"),
|
||||
include_shader!("hdr_luminance.shader"),
|
||||
)?,
|
||||
hdr_downscale: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/hdr_downscale.shader"),
|
||||
include_shader!("hdr_downscale.shader"),
|
||||
)?,
|
||||
hdr_map: RenderPassContainer::from_str(server, include_str!("shaders/hdr_map.shader"))?,
|
||||
bloom: RenderPassContainer::from_str(server, include_str!("shaders/bloom.shader"))?,
|
||||
skybox: RenderPassContainer::from_str(server, include_str!("shaders/skybox.shader"))?,
|
||||
hdr_map: RenderPassContainer::from_str(server, include_shader!("hdr_map.shader"))?,
|
||||
bloom: RenderPassContainer::from_str(server, include_shader!("bloom.shader"))?,
|
||||
skybox: RenderPassContainer::from_str(server, include_shader!("skybox.shader"))?,
|
||||
gaussian_blur: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/gaussian_blur.shader"),
|
||||
include_shader!("gaussian_blur.shader"),
|
||||
)?,
|
||||
box_blur: RenderPassContainer::from_str(server, include_str!("shaders/blur.shader"))?,
|
||||
box_blur: RenderPassContainer::from_str(server, include_shader!("blur.shader"))?,
|
||||
ui: RenderPassContainer::from_str(
|
||||
server,
|
||||
str::from_utf8(
|
||||
@@ -185,11 +199,11 @@ impl ShadersContainer {
|
||||
)?,
|
||||
environment_map_specular_convolution: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/prefilter.shader"),
|
||||
include_shader!("prefilter.shader"),
|
||||
)?,
|
||||
environment_map_irradiance_convolution: RenderPassContainer::from_str(
|
||||
server,
|
||||
include_str!("shaders/irradiance.shader"),
|
||||
include_shader!("irradiance.shader"),
|
||||
)?,
|
||||
})
|
||||
}
|
||||
@@ -309,9 +323,11 @@ impl RendererResources {
|
||||
kind: BufferKind::Uniform,
|
||||
usage: BufferUsage::StaticDraw,
|
||||
})?;
|
||||
const SIZE: usize = ShaderDefinition::MAX_BONE_MATRICES * size_of::<Matrix4<f32>>();
|
||||
let zeros = [0.0; SIZE];
|
||||
buffer.write_data(array_as_u8_slice(&zeros))?;
|
||||
buffer.write_data(&vec![
|
||||
0u8;
|
||||
ShaderDefinition::MAX_BONE_MATRICES
|
||||
* size_of::<Matrix4<f32>>()
|
||||
])?;
|
||||
buffer
|
||||
},
|
||||
linear_clamp_sampler: server.create_sampler(GpuSamplerDescriptor {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
(
|
||||
name: "AmbientLight",
|
||||
resources: [
|
||||
(
|
||||
name: "diffuseTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "aoSampler",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 1
|
||||
),
|
||||
(
|
||||
name: "bakedLightingTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 2
|
||||
),
|
||||
(
|
||||
name: "depthTexture",
|
||||
kind: Texture(kind: DepthSampler2D, fallback: White),
|
||||
binding: 3
|
||||
),
|
||||
(
|
||||
name: "normalTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 4
|
||||
),
|
||||
(
|
||||
name: "materialTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 5
|
||||
),
|
||||
(
|
||||
name: "prefilteredSpecularMap",
|
||||
kind: Texture(kind: SamplerCube, fallback: White),
|
||||
binding: 6
|
||||
),
|
||||
(
|
||||
name: "irradianceMap",
|
||||
kind: Texture(kind: SamplerCube, fallback: White),
|
||||
binding: 7
|
||||
),
|
||||
(
|
||||
name: "brdfLUT",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 8
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "ambientColor", kind: Vector4()),
|
||||
(name: "cameraPosition", kind: Vector3()),
|
||||
(name: "invViewProj", kind: Matrix4()),
|
||||
(name: "skyboxLighting", kind: Bool()),
|
||||
(name: "environmentLightingBrightness", kind: Float()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: SrcAlpha,
|
||||
dfactor: OneMinusSrcAlpha,
|
||||
alpha_sfactor: SrcAlpha,
|
||||
alpha_dfactor: OneMinusSrcAlpha,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertex_position: vec3f,
|
||||
@location(1) vertex_tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.tex_coord = input.vertex_tex_coord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) vec4f {
|
||||
let depth = textureSample(depthTexture_tex, depthTexture_samp, tex_coord);
|
||||
let fragment_position = S_UnProject(vec3f(tex_coord, depth), properties.invViewProj);
|
||||
|
||||
let albedo = S_SRGBToLinear(textureSample(diffuseTexture_tex, diffuseTexture_samp, tex_coord));
|
||||
|
||||
let fragment_normal = normalize(textureSample(normalTexture_tex, normalTexture_samp, tex_coord).xyz * 2.0 - 1.0);
|
||||
|
||||
let material = textureSample(materialTexture_tex, materialTexture_samp, tex_coord).rgb;
|
||||
let metallic = material.x;
|
||||
let roughness = material.y;
|
||||
let material_ao = material.z;
|
||||
|
||||
let view_vector = normalize(properties.cameraPosition - fragment_position);
|
||||
let reflection_vector = -reflect(view_vector, fragment_normal);
|
||||
|
||||
let clamped_cos_view_angle = max(dot(fragment_normal, view_vector), 0.0);
|
||||
|
||||
let cube_map_size = textureDimensions(prefilteredSpecularMap_tex, 0);
|
||||
let mip = roughness * (floor(log2(f32(cube_map_size.x))) + 1.0);
|
||||
|
||||
var reflection: vec3f;
|
||||
if (properties.skyboxLighting != 0u) {
|
||||
reflection = S_SRGBToLinear(textureSampleLevel(prefilteredSpecularMap_tex, prefilteredSpecularMap_samp, reflection_vector, mip)).rgb;
|
||||
} else {
|
||||
reflection = properties.ambientColor.rgb;
|
||||
}
|
||||
|
||||
let F0 = mix(vec3f(0.04), albedo.rgb, metallic);
|
||||
let F = S_FresnelSchlickRoughness(clamped_cos_view_angle, F0, roughness);
|
||||
let kD = (vec3f(1.0) - F) * (1.0 - metallic);
|
||||
|
||||
let envBRDF = textureSample(brdfLUT_tex, brdfLUT_samp, vec2f(clamped_cos_view_angle, roughness)).rg;
|
||||
let specular = reflection * (F * envBRDF.x + envBRDF.y);
|
||||
|
||||
let ambient_occlusion = textureSample(aoSampler_tex, aoSampler_samp, tex_coord).r * material_ao;
|
||||
let baked_lighting = textureSample(bakedLightingTexture_tex, bakedLightingTexture_samp, tex_coord);
|
||||
|
||||
let irradiance = S_SRGBToLinear(textureSample(irradianceMap_tex, irradianceMap_samp, fragment_normal)).rgb;
|
||||
|
||||
var ambient_lighting: vec3f;
|
||||
if (properties.skyboxLighting != 0u) {
|
||||
ambient_lighting = irradiance;
|
||||
} else {
|
||||
ambient_lighting = properties.ambientColor.rgb;
|
||||
}
|
||||
let diffuse = (baked_lighting.rgb + properties.environmentLightingBrightness * ambient_lighting) * albedo.rgb;
|
||||
|
||||
let output_rgb = (kD * diffuse + specular) * ambient_occlusion;
|
||||
return vec4f(output_rgb, baked_lighting.a);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,70 @@
|
||||
(
|
||||
name: "Blit",
|
||||
resources: [
|
||||
(
|
||||
name: "diffuseTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
||||
return textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,76 @@
|
||||
(
|
||||
name: "Bloom",
|
||||
resources: [
|
||||
(
|
||||
name: "hdrSampler",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "threshold", kind: Float(value: 1.01)),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
||||
let hdrPixel = textureSample(hdrSampler_tex, hdrSampler_samp, texCoord).rgb;
|
||||
if (S_Luminance(hdrPixel) > properties.threshold) {
|
||||
return vec4f(hdrPixel, 0.0);
|
||||
} else {
|
||||
return vec4f(0.0);
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,80 @@
|
||||
(
|
||||
name: "Blur",
|
||||
resources: [
|
||||
(
|
||||
name: "inputTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertex_position: vec3f,
|
||||
@location(1) vertex_tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.tex_coord = input.vertex_tex_coord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
// Simple 4x4 box blur.
|
||||
|
||||
@fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) f32 {
|
||||
let texel_size = 1.0 / vec2f(textureDimensions(inputTexture_tex, 0));
|
||||
var result: f32 = 0.0;
|
||||
for (var y: i32 = -2; y < 2; y++) {
|
||||
for (var x: i32 = -2; x < 2; x++) {
|
||||
let offset = vec2f(f32(x), f32(y)) * texel_size;
|
||||
result += textureSample(inputTexture_tex, inputTexture_samp, tex_coord + offset).r;
|
||||
}
|
||||
}
|
||||
return result / 16.0;
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,65 @@
|
||||
(
|
||||
name: "Debug",
|
||||
resources: [
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: Some(LessOrEqual),
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexColor: vec4f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) color: vec4f,
|
||||
};
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.color = input.vertexColor;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) color: vec4f) -> @location(0) vec4f {
|
||||
return color;
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,150 @@
|
||||
(
|
||||
name: "Decal",
|
||||
resources: [
|
||||
(
|
||||
name: "sceneDepth",
|
||||
kind: Texture(kind: DepthSampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "diffuseTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 1
|
||||
),
|
||||
(
|
||||
name: "normalTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 2
|
||||
),
|
||||
(
|
||||
name: "decalMask",
|
||||
kind: Texture(kind: USampler2D, fallback: White),
|
||||
binding: 3
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "invViewProj", kind: Matrix4()),
|
||||
(name: "invWorldDecal", kind: Matrix4()),
|
||||
(name: "resolution", kind: Vector2()),
|
||||
(name: "color", kind: Vector4()),
|
||||
(name: "layerIndex", kind: UInt()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: SrcAlpha,
|
||||
dfactor: OneMinusSrcAlpha,
|
||||
alpha_sfactor: SrcAlpha,
|
||||
alpha_dfactor: OneMinusSrcAlpha,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertex_position: vec3f,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) clip_space_position: vec4f,
|
||||
}
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0);
|
||||
output.clip_space_position = output.position;
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
struct FragmentOutput {
|
||||
@location(0) out_diffuse_map: vec4f,
|
||||
@location(1) out_normal_map: vec4f,
|
||||
}
|
||||
|
||||
@fragment fn fs_main(@location(0) clip_space_position: vec4f) -> FragmentOutput {
|
||||
let screen_pos = clip_space_position.xy / clip_space_position.w;
|
||||
|
||||
let tex_coord = vec2f(
|
||||
(1.0 + screen_pos.x) / 2.0 + (0.5 / properties.resolution.x),
|
||||
(1.0 + screen_pos.y) / 2.0 + (0.5 / properties.resolution.y)
|
||||
);
|
||||
|
||||
let mask_index = textureLoad(decalMask_tex, vec2i(tex_coord * vec2f(textureDimensions(decalMask_tex))), 0);
|
||||
|
||||
// Masking.
|
||||
if (mask_index.r != properties.layerIndex) {
|
||||
discard;
|
||||
}
|
||||
|
||||
let scene_depth = textureSample(sceneDepth_tex, sceneDepth_samp, tex_coord);
|
||||
|
||||
let scene_world_position = S_UnProject(vec3f(tex_coord, scene_depth), properties.invViewProj);
|
||||
|
||||
let decal_space_position = (properties.invWorldDecal * vec4f(scene_world_position, 1.0)).xyz;
|
||||
|
||||
// Check if scene pixel is not inside decal bounds.
|
||||
let dpos = vec3f(0.5) - abs(decal_space_position);
|
||||
if (dpos.x < 0.0 || dpos.y < 0.0 || dpos.z < 0.0) {
|
||||
discard;
|
||||
}
|
||||
|
||||
let decal_tex_coord = decal_space_position.xz + 0.5;
|
||||
|
||||
let diffuse = properties.color * textureSample(diffuseTexture_tex, diffuseTexture_samp, decal_tex_coord);
|
||||
|
||||
let fragment_tangent = dpdx(scene_world_position);
|
||||
let fragment_binormal = dpdy(scene_world_position);
|
||||
let fragment_normal = cross(fragment_tangent, fragment_binormal);
|
||||
|
||||
var tangent_to_world: mat3x3f;
|
||||
tangent_to_world[0] = normalize(fragment_tangent); // Tangent
|
||||
tangent_to_world[1] = normalize(fragment_binormal); // Binormal
|
||||
tangent_to_world[2] = normalize(fragment_normal); // Normal
|
||||
|
||||
let raw_normal = (textureSample(normalTexture_tex, normalTexture_samp, decal_tex_coord) * 2.0 - 1.0).xyz;
|
||||
let world_space_normal = tangent_to_world * raw_normal;
|
||||
|
||||
var output: FragmentOutput;
|
||||
output.out_diffuse_map = diffuse;
|
||||
output.out_normal_map = vec4f(world_space_normal * 0.5 + 0.5, diffuse.a);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,155 @@
|
||||
(
|
||||
name: "DeferredDirectionalLight",
|
||||
resources: [
|
||||
(
|
||||
name: "depthTexture",
|
||||
kind: Texture(kind: DepthSampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "colorTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 1
|
||||
),
|
||||
(
|
||||
name: "normalTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 2
|
||||
),
|
||||
(
|
||||
name: "materialTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 3
|
||||
),
|
||||
(
|
||||
name: "shadowCascade0",
|
||||
kind: Texture(kind: DepthSampler2D, fallback: White),
|
||||
binding: 4
|
||||
),
|
||||
(
|
||||
name: "shadowCascade1",
|
||||
kind: Texture(kind: DepthSampler2D, fallback: White),
|
||||
binding: 5
|
||||
),
|
||||
(
|
||||
name: "shadowCascade2",
|
||||
kind: Texture(kind: DepthSampler2D, fallback: White),
|
||||
binding: 6
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "viewMatrix", kind: Matrix4()),
|
||||
(name: "invViewProj", kind: Matrix4()),
|
||||
(name: "lightViewProjMatrices", kind: Matrix4Array(max_len: 3, value: [])),
|
||||
(name: "lightColor", kind: Vector4()),
|
||||
(name: "lightDirection", kind: Vector3()),
|
||||
(name: "cameraPosition", kind: Vector3()),
|
||||
(name: "lightIntensity", kind: Float()),
|
||||
(name: "shadowsEnabled", kind: Bool()),
|
||||
(name: "shadowBias", kind: Float()),
|
||||
(name: "softShadows", kind: Bool()),
|
||||
(name: "cascadeDistances", kind: FloatArray(max_len: 3, value: [])),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: One,
|
||||
dfactor: One,
|
||||
alpha_sfactor: One,
|
||||
alpha_dfactor: One,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertex_position: vec3f,
|
||||
@location(1) vertex_tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0);
|
||||
output.tex_coord = input.vertex_tex_coord;
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) vec4f {
|
||||
let material = textureSample(materialTexture_tex, materialTexture_samp, tex_coord).rgb;
|
||||
|
||||
let fragment_position = S_UnProject(vec3f(tex_coord, textureSample(depthTexture_tex, depthTexture_samp, tex_coord)), properties.invViewProj);
|
||||
let diffuse_color = textureSample(colorTexture_tex, colorTexture_samp, tex_coord);
|
||||
|
||||
var ctx: TPBRContext;
|
||||
ctx.albedo = S_SRGBToLinear(diffuse_color).rgb;
|
||||
ctx.fragmentToLight = properties.lightDirection;
|
||||
ctx.fragmentNormal = normalize(textureSample(normalTexture_tex, normalTexture_samp, tex_coord).xyz * 2.0 - 1.0);
|
||||
ctx.lightColor = properties.lightColor.rgb;
|
||||
ctx.metallic = material.x;
|
||||
ctx.roughness = material.y;
|
||||
ctx.viewVector = normalize(properties.cameraPosition - fragment_position);
|
||||
|
||||
let lighting = S_PBR_CalculateLight(ctx);
|
||||
|
||||
let fragment_z_view_space = abs((properties.viewMatrix * vec4f(fragment_position, 1.0)).z);
|
||||
|
||||
var shadow: f32 = 1.0;
|
||||
if (fragment_z_view_space <= properties.cascadeDistances[0].x) {
|
||||
let inv_size = 1.0 / f32(textureDimensions(shadowCascade0_tex).x);
|
||||
shadow = S_SpotShadowFactor_Depth(properties.shadowsEnabled != 0u, properties.softShadows != 0u,
|
||||
properties.shadowBias, fragment_position, properties.lightViewProjMatrices[0], inv_size, shadowCascade0_tex, shadowCascade0_samp);
|
||||
} else if (fragment_z_view_space <= properties.cascadeDistances[1].x) {
|
||||
let inv_size = 1.0 / f32(textureDimensions(shadowCascade1_tex).x);
|
||||
shadow = S_SpotShadowFactor_Depth(properties.shadowsEnabled != 0u, properties.softShadows != 0u,
|
||||
properties.shadowBias, fragment_position, properties.lightViewProjMatrices[1], inv_size, shadowCascade1_tex, shadowCascade1_samp);
|
||||
} else if (fragment_z_view_space <= properties.cascadeDistances[2].x) {
|
||||
let inv_size = 1.0 / f32(textureDimensions(shadowCascade2_tex).x);
|
||||
shadow = S_SpotShadowFactor_Depth(properties.shadowsEnabled != 0u, properties.softShadows != 0u,
|
||||
properties.shadowBias, fragment_position, properties.lightViewProjMatrices[2], inv_size, shadowCascade2_tex, shadowCascade2_samp);
|
||||
}
|
||||
|
||||
return shadow * vec4f(properties.lightIntensity * lighting, diffuse_color.a);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,140 @@
|
||||
(
|
||||
name: "DeferredPointLight",
|
||||
resources: [
|
||||
(
|
||||
name: "depthTexture",
|
||||
kind: Texture(kind: DepthSampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "colorTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 1
|
||||
),
|
||||
(
|
||||
name: "normalTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 2
|
||||
),
|
||||
(
|
||||
name: "materialTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 3
|
||||
),
|
||||
(
|
||||
name: "pointShadowTexture",
|
||||
kind: Texture(kind: SamplerCube, fallback: White),
|
||||
binding: 4
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "invViewProj", kind: Matrix4()),
|
||||
(name: "lightColor", kind: Vector4()),
|
||||
(name: "lightPos", kind: Vector3()),
|
||||
(name: "cameraPosition", kind: Vector3()),
|
||||
(name: "lightRadius", kind: Float()),
|
||||
(name: "shadowBias", kind: Float()),
|
||||
(name: "lightIntensity", kind: Float()),
|
||||
(name: "shadowAlpha", kind: Float()),
|
||||
(name: "softShadows", kind: Bool()),
|
||||
(name: "shadowsEnabled", kind: Bool()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: Some(StencilFunc(
|
||||
func: NotEqual,
|
||||
ref_value: 0,
|
||||
mask: 0xFFFF_FFFF
|
||||
)),
|
||||
depth_test: None,
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: One,
|
||||
dfactor: One,
|
||||
alpha_sfactor: One,
|
||||
alpha_dfactor: One,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Zero,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertex_position: vec3f,
|
||||
@location(1) vertex_tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0);
|
||||
output.tex_coord = input.vertex_tex_coord;
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) vec4f {
|
||||
let material = textureSample(materialTexture_tex, materialTexture_samp, tex_coord).rgb;
|
||||
|
||||
let fragment_position = S_UnProject(vec3f(tex_coord, textureSample(depthTexture_tex, depthTexture_samp, tex_coord)), properties.invViewProj);
|
||||
let fragment_to_light = properties.lightPos - fragment_position;
|
||||
let dist = length(fragment_to_light);
|
||||
|
||||
let diffuse_color = textureSample(colorTexture_tex, colorTexture_samp, tex_coord);
|
||||
|
||||
var ctx: TPBRContext;
|
||||
ctx.albedo = S_SRGBToLinear(diffuse_color).rgb;
|
||||
ctx.fragmentToLight = fragment_to_light / dist;
|
||||
ctx.fragmentNormal = normalize(textureSample(normalTexture_tex, normalTexture_samp, tex_coord).xyz * 2.0 - 1.0);
|
||||
ctx.lightColor = properties.lightColor.rgb;
|
||||
ctx.metallic = material.x;
|
||||
ctx.roughness = material.y;
|
||||
ctx.viewVector = normalize(properties.cameraPosition - fragment_position);
|
||||
|
||||
let lighting = S_PBR_CalculateLight(ctx);
|
||||
|
||||
let distance_attenuation = S_LightDistanceAttenuation(dist, properties.lightRadius);
|
||||
|
||||
let shadow = S_PointShadow(
|
||||
properties.shadowsEnabled != 0u, properties.softShadows != 0u, dist, properties.shadowBias, ctx.fragmentToLight, pointShadowTexture_tex, pointShadowTexture_samp);
|
||||
let final_shadow = mix(1.0, shadow, properties.shadowAlpha);
|
||||
|
||||
return vec4f(properties.lightIntensity * distance_attenuation * final_shadow * lighting, diffuse_color.a);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,160 @@
|
||||
(
|
||||
name: "DeferredSpotLight",
|
||||
resources: [
|
||||
(
|
||||
name: "depthTexture",
|
||||
kind: Texture(kind: DepthSampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "colorTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 1
|
||||
),
|
||||
(
|
||||
name: "normalTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 2
|
||||
),
|
||||
(
|
||||
name: "materialTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 3
|
||||
),
|
||||
(
|
||||
name: "spotShadowTexture",
|
||||
kind: Texture(kind: DepthSampler2D, fallback: White),
|
||||
binding: 4
|
||||
),
|
||||
(
|
||||
name: "cookieTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 5
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "lightViewProjMatrix", kind: Matrix4()),
|
||||
(name: "invViewProj", kind: Matrix4()),
|
||||
(name: "lightPos", kind: Vector3()),
|
||||
(name: "lightColor", kind: Vector4()),
|
||||
(name: "cameraPosition", kind: Vector3()),
|
||||
(name: "lightDirection", kind: Vector3()),
|
||||
(name: "lightRadius", kind: Float()),
|
||||
(name: "halfHotspotConeAngleCos", kind: Float()),
|
||||
(name: "halfConeAngleCos", kind: Float()),
|
||||
(name: "shadowMapInvSize", kind: Float()),
|
||||
(name: "shadowBias", kind: Float()),
|
||||
(name: "lightIntensity", kind: Float()),
|
||||
(name: "shadowAlpha", kind: Float()),
|
||||
(name: "cookieEnabled", kind: Bool()),
|
||||
(name: "shadowsEnabled", kind: Bool()),
|
||||
(name: "softShadows", kind: Bool()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: Some(StencilFunc(
|
||||
func: NotEqual,
|
||||
ref_value: 0,
|
||||
mask: 0xFFFF_FFFF
|
||||
)),
|
||||
depth_test: None,
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: One,
|
||||
dfactor: One,
|
||||
alpha_sfactor: One,
|
||||
alpha_dfactor: One,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Zero,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertex_position: vec3f,
|
||||
@location(1) vertex_tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0);
|
||||
output.tex_coord = input.vertex_tex_coord;
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) vec4f {
|
||||
let material = textureSample(materialTexture_tex, materialTexture_samp, tex_coord).rgb;
|
||||
|
||||
let fragment_position = S_UnProject(vec3f(tex_coord, textureSample(depthTexture_tex, depthTexture_samp, tex_coord)), properties.invViewProj);
|
||||
let fragment_to_light = properties.lightPos - fragment_position;
|
||||
let dist = length(fragment_to_light);
|
||||
let diffuse_color = textureSample(colorTexture_tex, colorTexture_samp, tex_coord);
|
||||
|
||||
var ctx: TPBRContext;
|
||||
ctx.albedo = S_SRGBToLinear(diffuse_color).rgb;
|
||||
ctx.fragmentToLight = fragment_to_light / dist;
|
||||
ctx.fragmentNormal = normalize(textureSample(normalTexture_tex, normalTexture_samp, tex_coord).xyz * 2.0 - 1.0);
|
||||
ctx.lightColor = properties.lightColor.rgb;
|
||||
ctx.metallic = material.x;
|
||||
ctx.roughness = material.y;
|
||||
ctx.viewVector = normalize(properties.cameraPosition - fragment_position);
|
||||
|
||||
let lighting = S_PBR_CalculateLight(ctx);
|
||||
|
||||
let distance_attenuation = S_LightDistanceAttenuation(dist, properties.lightRadius);
|
||||
|
||||
let spot_angle_cos = dot(properties.lightDirection, ctx.fragmentToLight);
|
||||
let cone_factor = smoothstep(properties.halfConeAngleCos, properties.halfHotspotConeAngleCos, spot_angle_cos);
|
||||
|
||||
let shadow = S_SpotShadowFactor_Depth(
|
||||
properties.shadowsEnabled != 0u, properties.softShadows != 0u, properties.shadowBias, fragment_position,
|
||||
properties.lightViewProjMatrix, properties.shadowMapInvSize, spotShadowTexture_tex, spotShadowTexture_samp);
|
||||
let final_shadow = mix(1.0, shadow, properties.shadowAlpha);
|
||||
|
||||
var cookie_attenuation = vec4f(1.0);
|
||||
if (properties.cookieEnabled != 0u) {
|
||||
let cookie_tex_coords = S_Project(fragment_position, properties.lightViewProjMatrix).xy;
|
||||
cookie_attenuation = textureSample(cookieTexture_tex, cookieTexture_samp, cookie_tex_coords);
|
||||
}
|
||||
|
||||
return cookie_attenuation * vec4f(distance_attenuation * properties.lightIntensity * cone_factor * final_shadow * lighting, diffuse_color.a);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,209 @@
|
||||
(
|
||||
name: "FXAA",
|
||||
resources: [
|
||||
(
|
||||
name: "screenTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "inverseScreenSize", kind: Vector2()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
// NVIDIA FXAA 3.11
|
||||
// Original source code by TIMOTHY LOTTES
|
||||
// WGSL port
|
||||
|
||||
const EDGE_THRESHOLD_MIN: f32 = 0.0312;
|
||||
const EDGE_THRESHOLD_MAX: f32 = 0.125;
|
||||
const ITERATIONS: i32 = 12;
|
||||
const SUBPIXEL_QUALITY: f32 = 0.75;
|
||||
|
||||
fn quality(q: i32) -> f32 {
|
||||
if (q < 5) { return 1.0; }
|
||||
if (q > 5) {
|
||||
if (q < 10) { return 2.0; }
|
||||
if (q < 11) { return 4.0; }
|
||||
return 8.0;
|
||||
}
|
||||
return 1.5;
|
||||
}
|
||||
|
||||
fn rgb2luma(rgb: vec3f) -> f32 {
|
||||
return sqrt(dot(rgb, vec3f(0.299, 0.587, 0.114)));
|
||||
}
|
||||
|
||||
@fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
||||
let colorCenter = textureSample(screenTexture_tex, screenTexture_samp, texCoord);
|
||||
let lumaCenter = rgb2luma(colorCenter.rgb);
|
||||
|
||||
let lumaDown = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(0.0, -properties.inverseScreenSize.y), 0.0).rgb);
|
||||
let lumaUp = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(0.0, properties.inverseScreenSize.y), 0.0).rgb);
|
||||
let lumaLeft = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(-properties.inverseScreenSize.x, 0.0), 0.0).rgb);
|
||||
let lumaRight = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(properties.inverseScreenSize.x, 0.0), 0.0).rgb);
|
||||
|
||||
let lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight)));
|
||||
let lumaMax = max(lumaCenter, max(max(lumaDown, lumaUp), max(lumaLeft, lumaRight)));
|
||||
let lumaRange = lumaMax - lumaMin;
|
||||
|
||||
if (lumaRange < max(EDGE_THRESHOLD_MIN, lumaMax * EDGE_THRESHOLD_MAX)) {
|
||||
return colorCenter;
|
||||
}
|
||||
|
||||
let lumaDownLeft = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(-properties.inverseScreenSize.x, -properties.inverseScreenSize.y), 0.0).rgb);
|
||||
let lumaUpRight = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(properties.inverseScreenSize.x, properties.inverseScreenSize.y), 0.0).rgb);
|
||||
let lumaUpLeft = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(-properties.inverseScreenSize.x, properties.inverseScreenSize.y), 0.0).rgb);
|
||||
let lumaDownRight = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(properties.inverseScreenSize.x, -properties.inverseScreenSize.y), 0.0).rgb);
|
||||
|
||||
let lumaDownUp = lumaDown + lumaUp;
|
||||
let lumaLeftRight = lumaLeft + lumaRight;
|
||||
let lumaLeftCorners = lumaDownLeft + lumaUpLeft;
|
||||
let lumaDownCorners = lumaDownLeft + lumaDownRight;
|
||||
let lumaRightCorners = lumaDownRight + lumaUpRight;
|
||||
let lumaUpCorners = lumaUpRight + lumaUpLeft;
|
||||
|
||||
let edgeHorizontal = abs(-2.0 * lumaLeft + lumaLeftCorners) + abs(-2.0 * lumaCenter + lumaDownUp) * 2.0 + abs(-2.0 * lumaRight + lumaRightCorners);
|
||||
let edgeVertical = abs(-2.0 * lumaUp + lumaUpCorners) + abs(-2.0 * lumaCenter + lumaLeftRight) * 2.0 + abs(-2.0 * lumaDown + lumaDownCorners);
|
||||
|
||||
let isHorizontal = (edgeHorizontal >= edgeVertical);
|
||||
var stepLength = select(properties.inverseScreenSize.x, properties.inverseScreenSize.y, isHorizontal);
|
||||
|
||||
let luma1 = select(lumaLeft, lumaDown, isHorizontal);
|
||||
let luma2 = select(lumaRight, lumaUp, isHorizontal);
|
||||
let gradient1 = luma1 - lumaCenter;
|
||||
let gradient2 = luma2 - lumaCenter;
|
||||
let is1Steepest = abs(gradient1) >= abs(gradient2);
|
||||
let gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2));
|
||||
|
||||
var lumaLocalAverage: f32;
|
||||
if (is1Steepest) {
|
||||
stepLength = -stepLength;
|
||||
lumaLocalAverage = 0.5 * (luma1 + lumaCenter);
|
||||
} else {
|
||||
lumaLocalAverage = 0.5 * (luma2 + lumaCenter);
|
||||
}
|
||||
|
||||
var currentUv = texCoord;
|
||||
if (isHorizontal) {
|
||||
currentUv.y += stepLength * 0.5;
|
||||
} else {
|
||||
currentUv.x += stepLength * 0.5;
|
||||
}
|
||||
|
||||
let offset = select(vec2f(0.0, properties.inverseScreenSize.y), vec2f(properties.inverseScreenSize.x, 0.0), isHorizontal);
|
||||
var uv1 = currentUv - offset * quality(0);
|
||||
var uv2 = currentUv + offset * quality(0);
|
||||
|
||||
var lumaEnd1 = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, uv1, 0.0).rgb) - lumaLocalAverage;
|
||||
var lumaEnd2 = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, uv2, 0.0).rgb) - lumaLocalAverage;
|
||||
|
||||
var reached1 = abs(lumaEnd1) >= gradientScaled;
|
||||
var reached2 = abs(lumaEnd2) >= gradientScaled;
|
||||
var reachedBoth = reached1 && reached2;
|
||||
|
||||
if (!reached1) { uv1 -= offset * quality(1); }
|
||||
if (!reached2) { uv2 += offset * quality(1); }
|
||||
|
||||
if (!reachedBoth) {
|
||||
for (var i: i32 = 2; i < ITERATIONS; i++) {
|
||||
if (!reached1) {
|
||||
lumaEnd1 = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, uv1, 0.0).rgb) - lumaLocalAverage;
|
||||
}
|
||||
if (!reached2) {
|
||||
lumaEnd2 = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, uv2, 0.0).rgb) - lumaLocalAverage;
|
||||
}
|
||||
reached1 = abs(lumaEnd1) >= gradientScaled;
|
||||
reached2 = abs(lumaEnd2) >= gradientScaled;
|
||||
reachedBoth = reached1 && reached2;
|
||||
|
||||
if (!reached1) { uv1 -= offset * quality(i); }
|
||||
if (!reached2) { uv2 += offset * quality(i); }
|
||||
if (reachedBoth) { break; }
|
||||
}
|
||||
}
|
||||
|
||||
let distance1 = select(texCoord.y - uv1.y, texCoord.x - uv1.x, isHorizontal);
|
||||
let distance2 = select(uv2.y - texCoord.y, uv2.x - texCoord.x, isHorizontal);
|
||||
let isDirection1 = distance1 < distance2;
|
||||
let distanceFinal = min(distance1, distance2);
|
||||
let edgeThickness = (distance1 + distance2);
|
||||
let isLumaCenterSmaller = lumaCenter < lumaLocalAverage;
|
||||
let correctVariation1 = (lumaEnd1 < 0.0) != isLumaCenterSmaller;
|
||||
let correctVariation2 = (lumaEnd2 < 0.0) != isLumaCenterSmaller;
|
||||
let correctVariation = select(correctVariation2, correctVariation1, isDirection1);
|
||||
|
||||
let pixelOffset = -distanceFinal / edgeThickness + 0.5;
|
||||
var finalOffset = select(0.0, pixelOffset, correctVariation);
|
||||
|
||||
let lumaAverage = (1.0 / 12.0) * (2.0 * (lumaDownUp + lumaLeftRight) + lumaLeftCorners + lumaRightCorners);
|
||||
let subPixelOffset1 = clamp(abs(lumaAverage - lumaCenter) / lumaRange, 0.0, 1.0);
|
||||
let subPixelOffset2 = (-2.0 * subPixelOffset1 + 3.0) * subPixelOffset1 * subPixelOffset1;
|
||||
let subPixelOffsetFinal = subPixelOffset2 * subPixelOffset2 * SUBPIXEL_QUALITY;
|
||||
finalOffset = max(finalOffset, subPixelOffsetFinal);
|
||||
|
||||
var finalUv = texCoord;
|
||||
if (isHorizontal) {
|
||||
finalUv.y += finalOffset * stepLength;
|
||||
} else {
|
||||
finalUv.x += finalOffset * stepLength;
|
||||
}
|
||||
|
||||
let finalColor = textureSampleLevel(screenTexture_tex, screenTexture_samp, finalUv, 0.0).rgb;
|
||||
return vec4f(finalColor, 1.0);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,94 @@
|
||||
(
|
||||
name: "GaussianBlur",
|
||||
resources: [
|
||||
(
|
||||
name: "image",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "pixelSize", kind: Vector2()),
|
||||
(name: "horizontal", kind: Bool()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
||||
const weights = array<f32, 5>(0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
|
||||
|
||||
let center = textureSample(image_tex, image_samp, texCoord);
|
||||
|
||||
var result = center.rgb * weights[0];
|
||||
|
||||
if (properties.horizontal != 0u) {
|
||||
for (var i: i32 = 1; i < 5; i++) {
|
||||
let fi = f32(i);
|
||||
|
||||
result += textureSample(image_tex, image_samp, texCoord + vec2f(properties.pixelSize.x * fi, 0.0)).rgb * weights[i];
|
||||
result += textureSample(image_tex, image_samp, texCoord - vec2f(properties.pixelSize.x * fi, 0.0)).rgb * weights[i];
|
||||
}
|
||||
} else {
|
||||
for (var i: i32 = 1; i < 5; i++) {
|
||||
let fi = f32(i);
|
||||
|
||||
result += textureSample(image_tex, image_samp, texCoord + vec2f(0.0, properties.pixelSize.y * fi)).rgb * weights[i];
|
||||
result += textureSample(image_tex, image_samp, texCoord - vec2f(0.0, properties.pixelSize.y * fi)).rgb * weights[i];
|
||||
}
|
||||
}
|
||||
|
||||
return vec4f(result, center.a);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
(
|
||||
name: "HdrAdaptation",
|
||||
resources: [
|
||||
(
|
||||
name: "oldLumSampler",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "newLumSampler",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 1
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "speed", kind: Float()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Zero,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main() -> @location(0) f32 {
|
||||
let oldLum = textureSample(oldLumSampler_tex, oldLumSampler_samp, vec2f(0.5, 0.5)).r;
|
||||
let newLum = textureSample(newLumSampler_tex, newLumSampler_samp, vec2f(0.5, 0.5)).r;
|
||||
return oldLum + (newLum - oldLum) * properties.speed;
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,97 @@
|
||||
(
|
||||
name: "HdrDownscale",
|
||||
resources: [
|
||||
(
|
||||
name: "lumSampler",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "invSize", kind: Vector2()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) f32 {
|
||||
let x = properties.invSize.x;
|
||||
let y = properties.invSize.y;
|
||||
let twoX = 2.0 * x;
|
||||
let twoY = 2.0 * y;
|
||||
|
||||
let a = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x - twoX, texCoord.y + twoY)).r;
|
||||
let b = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x, texCoord.y + twoY)).r;
|
||||
let c = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x + twoX, texCoord.y + twoY)).r;
|
||||
|
||||
let d = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x - twoX, texCoord.y)).r;
|
||||
let e = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x, texCoord.y)).r;
|
||||
let f = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x + twoX, texCoord.y)).r;
|
||||
|
||||
let g = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x - twoX, texCoord.y - twoY)).r;
|
||||
let h = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x, texCoord.y - twoY)).r;
|
||||
let i = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x + twoX, texCoord.y - twoY)).r;
|
||||
|
||||
let j = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x - x, texCoord.y + y)).r;
|
||||
let k = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x + x, texCoord.y + y)).r;
|
||||
let l = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x - x, texCoord.y - y)).r;
|
||||
let m = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x + x, texCoord.y - y)).r;
|
||||
|
||||
var outLum = e * 0.125;
|
||||
outLum += (a + c + g + i) * 0.03125;
|
||||
outLum += (b + d + f + h) * 0.0625;
|
||||
outLum += (j + k + l + m) * 0.125;
|
||||
return outLum;
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,77 @@
|
||||
(
|
||||
name: "HdrLuminance",
|
||||
resources: [
|
||||
(
|
||||
name: "frameSampler",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "invSize", kind: Vector2()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) f32 {
|
||||
var totalLum: f32 = 0.0;
|
||||
for (var y: f32 = -0.5; y < 0.5; y += 0.5) {
|
||||
for (var x: f32 = -0.5; x < 0.5; x += 0.5) {
|
||||
totalLum += S_Luminance(textureSample(frameSampler_tex, frameSampler_samp, texCoord - vec2f(x, y) * properties.invSize).xyz);
|
||||
}
|
||||
}
|
||||
return totalLum / 9.0;
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,130 @@
|
||||
(
|
||||
name: "HdrMap",
|
||||
resources: [
|
||||
(
|
||||
name: "hdrSampler",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "lumSampler",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 1
|
||||
),
|
||||
(
|
||||
name: "bloomSampler",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 2
|
||||
),
|
||||
(
|
||||
name: "colorMapSampler",
|
||||
kind: Texture(kind: Sampler3D, fallback: White),
|
||||
binding: 3
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "useColorGrading", kind: Bool()),
|
||||
(name: "minLuminance", kind: Float()),
|
||||
(name: "maxLuminance", kind: Float()),
|
||||
(name: "autoExposure", kind: Bool()),
|
||||
(name: "fixedExposure", kind: Float()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
fn ColorGrading(color: vec3f) -> vec3f {
|
||||
const lutSize: f32 = 16.0;
|
||||
const a: f32 = (lutSize - 1.0) / lutSize;
|
||||
const b: f32 = 1.0 / (2.0 * lutSize);
|
||||
let scale = vec3f(a);
|
||||
let offset = vec3f(b);
|
||||
return textureSample(colorMapSampler_tex, colorMapSampler_samp, scale * color + offset).rgb;
|
||||
}
|
||||
|
||||
// Narkowicz 2015, "ACES Filmic Tone Mapping Curve"
|
||||
fn TonemapACES(x: f32) -> f32 {
|
||||
const a: f32 = 2.51;
|
||||
const b: f32 = 0.03;
|
||||
const c: f32 = 2.43;
|
||||
const d: f32 = 0.59;
|
||||
const e: f32 = 0.14;
|
||||
return (x * (a * x + b)) / (x * (c * x + d) + e);
|
||||
}
|
||||
|
||||
@fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
||||
let hdrColor = textureSample(hdrSampler_tex, hdrSampler_samp, texCoord) + textureSample(bloomSampler_tex, bloomSampler_samp, texCoord);
|
||||
|
||||
var Yxy = S_ConvertRgbToYxy(hdrColor.rgb);
|
||||
|
||||
var lp: f32;
|
||||
if (properties.autoExposure != 0u) {
|
||||
let avgLum = textureSample(lumSampler_tex, lumSampler_samp, vec2f(0.5, 0.5)).r;
|
||||
let clampedAvgLum = clamp(avgLum, properties.minLuminance, properties.maxLuminance);
|
||||
lp = Yxy.x / (9.6 * clampedAvgLum + 0.0001);
|
||||
} else {
|
||||
lp = Yxy.x * properties.fixedExposure;
|
||||
}
|
||||
|
||||
Yxy.x = TonemapACES(lp);
|
||||
|
||||
let ldrColor = vec4f(S_ConvertYxyToRgb(Yxy), hdrColor.a);
|
||||
|
||||
if (properties.useColorGrading != 0u) {
|
||||
return vec4f(ColorGrading(S_LinearToSRGB(ldrColor).rgb), ldrColor.a);
|
||||
} else {
|
||||
return S_LinearToSRGB(ldrColor);
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,96 @@
|
||||
(
|
||||
name: "IrradianceShader",
|
||||
resources: [
|
||||
(
|
||||
name: "environmentMap",
|
||||
kind: Texture(kind: SamplerCube, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) localPos: vec3f,
|
||||
};
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.localPos = input.vertexPosition;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) localPos: vec3f) -> @location(0) vec4f {
|
||||
let N = normalize(localPos);
|
||||
|
||||
var irradiance = vec3f(0.0);
|
||||
|
||||
var up = vec3f(0.0, 1.0, 0.0);
|
||||
let right = normalize(cross(up, N));
|
||||
up = normalize(cross(N, right));
|
||||
|
||||
let sampleDelta: f32 = 0.1;
|
||||
var nrSamples: f32 = 0.0;
|
||||
for (var phi: f32 = 0.0; phi < 2.0 * PI; phi += sampleDelta) {
|
||||
let cosPhi = cos(phi);
|
||||
let sinPhi = sin(phi);
|
||||
|
||||
for (var theta: f32 = 0.0; theta < 0.5 * PI; theta += sampleDelta) {
|
||||
let cosTheta = cos(theta);
|
||||
let sinTheta = sin(theta);
|
||||
|
||||
let tangentSample = vec3f(sinTheta * cosPhi, sinTheta * sinPhi, cosTheta);
|
||||
let sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;
|
||||
|
||||
irradiance += textureSample(environmentMap_tex, environmentMap_samp, sampleVec).rgb * cosTheta * sinTheta;
|
||||
nrSamples += 1.0;
|
||||
}
|
||||
}
|
||||
irradiance = PI * irradiance * (1.0 / nrSamples);
|
||||
|
||||
return vec4f(irradiance, 1.0);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,66 @@
|
||||
(
|
||||
name: "PixelCounter",
|
||||
resources: [
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: false,
|
||||
green: false,
|
||||
blue: false,
|
||||
alpha: false,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: Some(StencilFunc (
|
||||
func: NotEqual,
|
||||
ref_value: 0,
|
||||
mask: 0xFFFF_FFFF,
|
||||
)),
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertex_position: vec3f,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
}
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main() -> @location(0) vec4f {
|
||||
return vec4f(1.0);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
(
|
||||
name: "PointVolumetric",
|
||||
resources: [
|
||||
(
|
||||
name: "depthSampler",
|
||||
kind: Texture(kind: DepthSampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "invProj", kind: Matrix4()),
|
||||
(name: "lightPosition", kind: Vector3()),
|
||||
(name: "lightColor", kind: Vector3()),
|
||||
(name: "scatterFactor", kind: Vector3()),
|
||||
(name: "intensity", kind: Float()),
|
||||
(name: "lightRadius", kind: Float()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: Some(StencilFunc(
|
||||
func: Equal,
|
||||
ref_value: 0xFF,
|
||||
mask: 0xFFFF_FFFF
|
||||
)),
|
||||
depth_test: None,
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: One,
|
||||
dfactor: One,
|
||||
alpha_sfactor: One,
|
||||
alpha_dfactor: One,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Zero,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertex_position: vec3f,
|
||||
@location(1) vertex_tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.tex_coord = input.vertex_tex_coord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) vec4f {
|
||||
let fragmentPosition = S_UnProject(vec3f(tex_coord, textureSample(depthSampler_tex, depthSampler_samp, tex_coord)), properties.invProj);
|
||||
let fragmentDepth = length(fragmentPosition);
|
||||
let viewDirection = fragmentPosition / fragmentDepth;
|
||||
|
||||
// Find intersection
|
||||
var scatter = vec3f(0.0);
|
||||
var minDepth: f32;
|
||||
var maxDepth: f32;
|
||||
if (S_RaySphereIntersection(vec3f(0.0), viewDirection, properties.lightPosition, properties.lightRadius, &minDepth, &maxDepth))
|
||||
{
|
||||
// Perform depth test.
|
||||
if (minDepth > 0.0 || fragmentDepth > minDepth)
|
||||
{
|
||||
minDepth = max(minDepth, 0.0);
|
||||
maxDepth = clamp(maxDepth, 0.0, fragmentDepth);
|
||||
|
||||
let closestPoint = viewDirection * minDepth;
|
||||
|
||||
scatter = properties.scatterFactor * S_InScatter(closestPoint, viewDirection, properties.lightPosition, maxDepth - minDepth);
|
||||
}
|
||||
}
|
||||
|
||||
return vec4f(properties.lightColor.xyz * pow(clamp(properties.intensity * scatter, vec3f(0.0), vec3f(1.0)), vec3f(2.2)), 1.0);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,126 @@
|
||||
(
|
||||
name: "ReflectionCubeMapPrefilter",
|
||||
resources: [
|
||||
(
|
||||
name: "environmentMap",
|
||||
kind: Texture(kind: SamplerCube, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "roughness", kind: Float()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertex_position: vec3f,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) local_pos: vec3f,
|
||||
}
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.local_pos = input.vertex_position;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
fn RadicalInverse_VdC(bits_in: u32) -> f32 {
|
||||
var bits = bits_in;
|
||||
bits = (bits << 16u) | (bits >> 16u);
|
||||
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
|
||||
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
|
||||
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
|
||||
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
|
||||
return f32(bits) * 2.3283064e-10;
|
||||
}
|
||||
|
||||
fn Hammersley(i: u32, N: u32) -> vec2f {
|
||||
return vec2f(f32(i) / f32(N), RadicalInverse_VdC(i));
|
||||
}
|
||||
|
||||
fn ImportanceSampleGGX(Xi: vec2f, N: vec3f, roughness: f32) -> vec3f {
|
||||
let a = roughness * roughness;
|
||||
|
||||
let phi = 2.0 * PI * Xi.x;
|
||||
let cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a * a - 1.0) * Xi.y));
|
||||
let sinTheta = sqrt(1.0 - cosTheta * cosTheta);
|
||||
|
||||
var H: vec3f;
|
||||
H.x = cos(phi) * sinTheta;
|
||||
H.y = sin(phi) * sinTheta;
|
||||
H.z = cosTheta;
|
||||
|
||||
let up = select(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 0.0, 1.0), abs(N.z) < 0.999);
|
||||
let tangent = normalize(cross(up, N));
|
||||
let bitangent = cross(N, tangent);
|
||||
|
||||
let sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
|
||||
return normalize(sampleVec);
|
||||
}
|
||||
|
||||
@fragment fn fs_main(@location(0) local_pos: vec3f) -> @location(0) vec4f {
|
||||
let N = normalize(local_pos);
|
||||
let R = N;
|
||||
let V = R;
|
||||
|
||||
const SAMPLE_COUNT: u32 = 64u;
|
||||
var totalWeight: f32 = 0.0;
|
||||
var prefilteredColor = vec3f(0.0);
|
||||
for (var i: u32 = 0u; i < SAMPLE_COUNT; i++)
|
||||
{
|
||||
let Xi = Hammersley(i, SAMPLE_COUNT);
|
||||
let H = ImportanceSampleGGX(Xi, N, properties.roughness);
|
||||
let L = normalize(2.0 * dot(V, H) * H - V);
|
||||
|
||||
let NdotL = max(dot(N, L), 0.0);
|
||||
if (NdotL > 0.0)
|
||||
{
|
||||
prefilteredColor += textureSample(environmentMap_tex, environmentMap_samp, L).rgb * NdotL;
|
||||
totalWeight += NdotL;
|
||||
}
|
||||
}
|
||||
prefilteredColor = prefilteredColor / totalWeight;
|
||||
|
||||
return vec4f(prefilteredColor, 1.0);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
(
|
||||
name: "SkyBox",
|
||||
resources: [
|
||||
(
|
||||
name: "cubemapTexture",
|
||||
kind: Texture(kind: SamplerCube, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertex_position: vec3f,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec3f,
|
||||
}
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.tex_coord = input.vertex_position;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) tex_coord: vec3f) -> @location(0) vec4f {
|
||||
return S_SRGBToLinear(textureSample(cubemapTexture_tex, cubemapTexture_samp, tex_coord));
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,137 @@
|
||||
(
|
||||
name: "SpotVolumetric",
|
||||
resources: [
|
||||
(
|
||||
name: "depthSampler",
|
||||
kind: Texture(kind: DepthSampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "invProj", kind: Matrix4()),
|
||||
(name: "lightPosition", kind: Vector3()),
|
||||
(name: "lightDirection", kind: Vector3()),
|
||||
(name: "lightColor", kind: Vector3()),
|
||||
(name: "scatterFactor", kind: Vector3()),
|
||||
(name: "intensity", kind: Float()),
|
||||
(name: "coneAngleCos", kind: Float()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: Some(StencilFunc(
|
||||
func: Equal,
|
||||
ref_value: 0xFF,
|
||||
mask: 0xFFFF_FFFF
|
||||
)),
|
||||
depth_test: None,
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: One,
|
||||
dfactor: One,
|
||||
alpha_sfactor: One,
|
||||
alpha_dfactor: One,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Zero,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertex_position: vec3f,
|
||||
@location(1) vertex_tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.tex_coord = input.vertex_tex_coord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) vec4f {
|
||||
let fragmentPosition = S_UnProject(vec3f(tex_coord, textureSample(depthSampler_tex, depthSampler_samp, tex_coord)), properties.invProj);
|
||||
let fragmentDepth = length(fragmentPosition);
|
||||
let viewDirection = fragmentPosition / fragmentDepth;
|
||||
|
||||
// Ray-cone intersection
|
||||
let sqrConeAngleCos = properties.coneAngleCos * properties.coneAngleCos;
|
||||
let CO = -properties.lightPosition;
|
||||
let DdotV = dot(viewDirection, properties.lightDirection);
|
||||
let COdotV = dot(CO, properties.lightDirection);
|
||||
let a = DdotV * DdotV - sqrConeAngleCos;
|
||||
let b = 2.0 * (DdotV * COdotV - dot(viewDirection, CO) * sqrConeAngleCos);
|
||||
let c = COdotV * COdotV - dot(CO, CO) * sqrConeAngleCos;
|
||||
|
||||
// Find intersection
|
||||
var scatter = vec3f(0.0);
|
||||
var minDepth: f32;
|
||||
var maxDepth: f32;
|
||||
if (S_SolveQuadraticEq(a, b, c, &minDepth, &maxDepth))
|
||||
{
|
||||
let dt1 = dot((minDepth * viewDirection) - properties.lightPosition, properties.lightDirection);
|
||||
let dt2 = dot((maxDepth * viewDirection) - properties.lightPosition, properties.lightDirection);
|
||||
|
||||
// Discard points on reflected cylinder and perform depth test.
|
||||
if ((dt1 > 0.0 || dt2 > 0.0) && (minDepth > 0.0 || fragmentDepth > minDepth))
|
||||
{
|
||||
if (dt1 > 0.0 && dt2 < 0.0)
|
||||
{
|
||||
// Closest point is on cylinder, farthest on reflected.
|
||||
maxDepth = minDepth;
|
||||
minDepth = 0.0;
|
||||
}
|
||||
else if (dt1 < 0.0 && dt2 > 0.0)
|
||||
{
|
||||
// Farthest point is on cylinder, closest on reflected.
|
||||
minDepth = maxDepth;
|
||||
maxDepth = fragmentDepth;
|
||||
}
|
||||
|
||||
minDepth = max(minDepth, 0.0);
|
||||
maxDepth = clamp(maxDepth, 0.0, fragmentDepth);
|
||||
|
||||
scatter = properties.scatterFactor * S_InScatter(viewDirection * minDepth, viewDirection, properties.lightPosition, maxDepth - minDepth);
|
||||
}
|
||||
}
|
||||
|
||||
return vec4f(properties.lightColor.xyz * pow(clamp(properties.intensity * scatter, vec3f(0.0), vec3f(1.0)), vec3f(2.2)), 1.0);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
(
|
||||
name: "SSAO",
|
||||
resources: [
|
||||
(
|
||||
name: "depthSampler",
|
||||
kind: Texture(kind: DepthSampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "normalSampler",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 1
|
||||
),
|
||||
(
|
||||
name: "noiseSampler",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 2
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
(name: "inverseProjectionMatrix", kind: Matrix4()),
|
||||
(name: "projectionMatrix", kind: Matrix4()),
|
||||
(name: "kernel", kind: Vector3Array(max_len: 32, value: [])),
|
||||
(name: "noiseScale", kind: Vector2()),
|
||||
(name: "viewMatrix", kind: Matrix3()),
|
||||
(name: "radius", kind: Float()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: None,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertex_position: vec3f,
|
||||
@location(1) vertex_tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
@vertex fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.tex_coord = input.vertex_tex_coord;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
fn GetViewSpacePosition(screenCoord: vec2f) -> vec3f {
|
||||
return S_UnProject(vec3f(screenCoord, textureSample(depthSampler_tex, depthSampler_samp, screenCoord)), properties.inverseProjectionMatrix);
|
||||
}
|
||||
|
||||
@fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) f32 {
|
||||
let fragPos = GetViewSpacePosition(tex_coord);
|
||||
let worldSpaceNormal = textureSample(normalSampler_tex, normalSampler_samp, tex_coord).xyz * 2.0 - 1.0;
|
||||
let viewSpaceNormal = normalize(properties.viewMatrix * worldSpaceNormal);
|
||||
let randomVec = normalize(textureSample(noiseSampler_tex, noiseSampler_samp, tex_coord * properties.noiseScale).xyz * 2.0 - 1.0);
|
||||
|
||||
let tangent = normalize(randomVec - viewSpaceNormal * dot(randomVec, viewSpaceNormal));
|
||||
let bitangent = normalize(cross(viewSpaceNormal, tangent));
|
||||
let TBN = mat3x3f(tangent, bitangent, viewSpaceNormal);
|
||||
|
||||
var occlusion: f32 = 0.0;
|
||||
const kernelSize: i32 = 32;
|
||||
for (var i: i32 = 0; i < kernelSize; i++) {
|
||||
let samplePoint = fragPos + TBN * properties.kernel[i] * properties.radius;
|
||||
|
||||
let offset = properties.projectionMatrix * vec4f(samplePoint, 1.0);
|
||||
let screenUv = (offset.xy / offset.w) * 0.5 + 0.5;
|
||||
|
||||
let position = GetViewSpacePosition(screenUv);
|
||||
|
||||
let rangeCheck = smoothstep(0.0, 1.0, properties.radius / abs(fragPos.z - position.z));
|
||||
occlusion += rangeCheck * select(0.0, 1.0, position.z > samplePoint.z + 0.04);
|
||||
}
|
||||
|
||||
return 1.0 - occlusion / f32(kernelSize);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
(
|
||||
name: "Visibility",
|
||||
resources: [
|
||||
(
|
||||
name: "matrices",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "tileBuffer",
|
||||
kind: Texture(kind: USampler2D, fallback: White),
|
||||
binding: 1
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "viewProjection", kind: Matrix4()),
|
||||
(name: "tileSize", kind: Int()),
|
||||
(name: "frameBufferHeight", kind: Float()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: Some(Back),
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: Some(LessOrEqual),
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: One,
|
||||
dfactor: One,
|
||||
alpha_sfactor: One,
|
||||
alpha_dfactor: One,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Zero,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) @interpolate(flat) objectIndex: u32,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput, @builtin(instance_index) instance_index: u32) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.objectIndex = instance_index;
|
||||
output.position = (properties.viewProjection * S_FetchMatrix(matrices_tex, matrices_samp, i32(instance_index))) * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment
|
||||
fn fs_main(@location(0) @interpolate(flat) objectIndex: u32, @builtin(position) fragCoord: vec4f) -> @location(0) vec4f {
|
||||
var x = i32(fragCoord.x) / properties.tileSize;
|
||||
var y = i32(properties.frameBufferHeight - fragCoord.y) / properties.tileSize;
|
||||
|
||||
var bitIndex: i32 = -1;
|
||||
var tileDataIndex = x * 33;
|
||||
var count = i32(textureLoad(tileBuffer_tex, vec2i(tileDataIndex, y), 0).x);
|
||||
var objectsListStartIndex = tileDataIndex + 1;
|
||||
for (var i: i32 = 0; i < count; i++) {
|
||||
var pixelObjectIndex = u32(textureLoad(tileBuffer_tex, vec2i(objectsListStartIndex + i, y), 0).x);
|
||||
if (pixelObjectIndex == objectIndex) {
|
||||
bitIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bitIndex < 0) {
|
||||
return vec4f(0.0, 0.0, 0.0, 0.0);
|
||||
} else {
|
||||
var outMask = 1u << u32(bitIndex);
|
||||
var r = f32(outMask & 255u) / 255.0;
|
||||
var g = f32((outMask & 65280u) >> 8) / 255.0;
|
||||
var b = f32((outMask & 16711680u) >> 16) / 255.0;
|
||||
var a = f32((outMask & 4278190080u) >> 24) / 255.0;
|
||||
return vec4f(r, g, b, a);
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,86 @@
|
||||
(
|
||||
name: "VisibilityOptimizer",
|
||||
resources: [
|
||||
(
|
||||
name: "visibilityBuffer",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "viewProjection", kind: Matrix4()),
|
||||
(name: "tileSize", kind: Int()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: None,
|
||||
depth_test: Some(LessOrEqual),
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Zero,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.position = properties.viewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment
|
||||
fn fs_main(@builtin(position) fragCoord: vec4f) -> @location(0) u32 {
|
||||
var tileX = i32(fragCoord.x);
|
||||
var tileY = i32(fragCoord.y);
|
||||
|
||||
var beginX = tileX * properties.tileSize;
|
||||
var beginY = tileY * properties.tileSize;
|
||||
|
||||
var endX = (tileX + 1) * properties.tileSize;
|
||||
var endY = (tileY + 1) * properties.tileSize;
|
||||
|
||||
var visibilityMask: i32 = 0;
|
||||
for (var y: i32 = beginY; y < endY; y++) {
|
||||
for (var x: i32 = beginX; x < endX; x++) {
|
||||
var mask = vec4i(textureLoad(visibilityBuffer_tex, vec2i(x, y), 0) * 255.0);
|
||||
visibilityMask |= (mask.w << 24) | (mask.z << 16) | (mask.y << 8) | mask.x;
|
||||
}
|
||||
}
|
||||
return u32(visibilityMask);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,45 @@
|
||||
(
|
||||
name: "VolumeMarkerLighting",
|
||||
resources: [
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
// Drawing params are dynamic.
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment
|
||||
fn fs_main() -> @location(0) vec4f {
|
||||
return vec4f(1.0);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
(
|
||||
name: "VolumeMarkerVolume",
|
||||
resources: [
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(name: "worldViewProjection", kind: Matrix4()),
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
],
|
||||
passes: [
|
||||
(
|
||||
name: "Primary",
|
||||
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: None,
|
||||
color_write: ColorMask(
|
||||
red: false,
|
||||
green: false,
|
||||
blue: false,
|
||||
alpha: false,
|
||||
),
|
||||
depth_write: false,
|
||||
stencil_test: Some(StencilFunc (
|
||||
func: Equal,
|
||||
ref_value: 0xFF,
|
||||
mask: 0xFFFF_FFFF
|
||||
)),
|
||||
depth_test: Some(Less),
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Replace,
|
||||
zfail: Keep,
|
||||
zpass: Replace,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@location(0) vertexPosition: vec3f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment
|
||||
fn fs_main() -> @location(0) vec4f {
|
||||
return vec4f(1.0);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -49,10 +49,24 @@ use crate::resource::texture::TextureMinificationFilter as FyroxMinFilter;
|
||||
use gltf::texture::MagFilter as GltfMagFilter;
|
||||
use gltf::texture::MinFilter as GltfMinFilter;
|
||||
|
||||
#[cfg(not(feature = "backend_opengl"))]
|
||||
macro_rules! embedded_gltf_shader {
|
||||
($file:literal) => {
|
||||
embedded_data_source!(concat!("wgpu/", $file))
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_opengl")]
|
||||
macro_rules! embedded_gltf_shader {
|
||||
($file:literal) => {
|
||||
embedded_data_source!(concat!("opengl/", $file))
|
||||
};
|
||||
}
|
||||
|
||||
pub static GLTF_SHADER: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|| {
|
||||
BuiltInResource::new(
|
||||
"GltfShader",
|
||||
embedded_data_source!("gltf_standard.shader"),
|
||||
embedded_gltf_shader!("gltf_standard.shader"),
|
||||
|data| {
|
||||
ShaderResource::new_ok(
|
||||
uuid!("33ee0142-f345-4c0a-9aca-d1f684a3485b"),
|
||||
|
||||
@@ -0,0 +1,673 @@
|
||||
(
|
||||
name: "GLTFShader",
|
||||
|
||||
resources: [
|
||||
(
|
||||
name: "diffuseTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "normalTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: Normal),
|
||||
binding: 1
|
||||
),
|
||||
(
|
||||
name: "metallicRoughnessTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 2
|
||||
),
|
||||
(
|
||||
name: "heightTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: Black),
|
||||
binding: 3
|
||||
),
|
||||
(
|
||||
name: "emissionTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 4
|
||||
),
|
||||
(
|
||||
name: "lightmapTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: Black),
|
||||
binding: 5
|
||||
),
|
||||
(
|
||||
name: "aoTexture",
|
||||
kind: Texture(kind: Sampler2D, fallback: White),
|
||||
binding: 6
|
||||
),
|
||||
(
|
||||
name: "blendShapesStorage",
|
||||
kind: Texture(kind: Sampler3D, fallback: Volume),
|
||||
binding: 7
|
||||
),
|
||||
(
|
||||
name: "properties",
|
||||
kind: PropertyGroup([
|
||||
(
|
||||
name: "texCoordScale",
|
||||
kind: Vector2(value: (1.0, 1.0)),
|
||||
),
|
||||
(
|
||||
name: "layerIndex",
|
||||
kind: UInt(value: 0),
|
||||
),
|
||||
(
|
||||
name: "emissionStrength",
|
||||
kind: Vector3(value: (2.0, 2.0, 2.0)),
|
||||
),
|
||||
(
|
||||
name: "diffuseColor",
|
||||
kind: Color(r: 255, g: 255, b: 255, a: 255),
|
||||
),
|
||||
(
|
||||
name: "parallaxCenter",
|
||||
kind: Float(value: 0.0),
|
||||
),
|
||||
(
|
||||
name: "parallaxScale",
|
||||
kind: Float(value: 0.08),
|
||||
),
|
||||
(
|
||||
name: "metallicFactor",
|
||||
kind: Float(value: 1.0)
|
||||
),
|
||||
(
|
||||
name: "roughnessFactor",
|
||||
kind: Float(value: 1.0)
|
||||
)
|
||||
]),
|
||||
binding: 0
|
||||
),
|
||||
(
|
||||
name: "fyrox_instanceData",
|
||||
kind: PropertyGroup([
|
||||
// Autogenerated
|
||||
]),
|
||||
binding: 1
|
||||
),
|
||||
(
|
||||
name: "fyrox_boneMatrices",
|
||||
kind: PropertyGroup([
|
||||
// Autogenerated
|
||||
]),
|
||||
binding: 2
|
||||
),
|
||||
(
|
||||
name: "fyrox_graphicsSettings",
|
||||
kind: PropertyGroup([
|
||||
// Autogenerated
|
||||
]),
|
||||
binding: 3
|
||||
),
|
||||
(
|
||||
name: "fyrox_cameraData",
|
||||
kind: PropertyGroup([
|
||||
// Autogenerated
|
||||
]),
|
||||
binding: 4
|
||||
),
|
||||
(
|
||||
name: "fyrox_lightData",
|
||||
kind: PropertyGroup([
|
||||
// Autogenerated
|
||||
]),
|
||||
binding: 5
|
||||
),
|
||||
],
|
||||
|
||||
passes: [
|
||||
(
|
||||
name: "GBuffer",
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: Some(Back),
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: Some(Less),
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
@location(2) vertexNormal: vec3f,
|
||||
@location(3) vertexTangent: vec4f,
|
||||
@location(4) boneWeights: vec4f,
|
||||
@location(5) boneIndices: vec4f,
|
||||
@location(6) vertexSecondTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) outPosition: vec3f,
|
||||
@location(1) normal: vec3f,
|
||||
@location(2) texCoord: vec2f,
|
||||
@location(3) tangent: vec3f,
|
||||
@location(4) binormal: vec3f,
|
||||
@location(5) secondTexCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
|
||||
var localPosition = vec4f(0.0);
|
||||
var localNormal = vec3f(0.0);
|
||||
var localTangent = vec3f(0.0);
|
||||
|
||||
var inputPosition = vec4f(input.vertexPosition, 1.0);
|
||||
var inputNormal = input.vertexNormal;
|
||||
var inputTangent = input.vertexTangent.xyz;
|
||||
|
||||
for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) {
|
||||
var offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i);
|
||||
var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4];
|
||||
inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w);
|
||||
inputNormal += offsets.normal * weight;
|
||||
inputTangent += offsets.tangent * weight;
|
||||
}
|
||||
|
||||
if (fyrox_instanceData.useSkeletalAnimation != 0u)
|
||||
{
|
||||
var i0 = i32(input.boneIndices.x);
|
||||
var i1 = i32(input.boneIndices.y);
|
||||
var i2 = i32(input.boneIndices.z);
|
||||
var i3 = i32(input.boneIndices.w);
|
||||
|
||||
var m0 = fyrox_boneMatrices.matrices[i0];
|
||||
var m1 = fyrox_boneMatrices.matrices[i1];
|
||||
var m2 = fyrox_boneMatrices.matrices[i2];
|
||||
var m3 = fyrox_boneMatrices.matrices[i3];
|
||||
|
||||
localPosition += m0 * inputPosition * input.boneWeights.x;
|
||||
localPosition += m1 * inputPosition * input.boneWeights.y;
|
||||
localPosition += m2 * inputPosition * input.boneWeights.z;
|
||||
localPosition += m3 * inputPosition * input.boneWeights.w;
|
||||
|
||||
localNormal += mat3x3f(m0[0].xyz, m0[1].xyz, m0[2].xyz) * inputNormal * input.boneWeights.x;
|
||||
localNormal += mat3x3f(m1[0].xyz, m1[1].xyz, m1[2].xyz) * inputNormal * input.boneWeights.y;
|
||||
localNormal += mat3x3f(m2[0].xyz, m2[1].xyz, m2[2].xyz) * inputNormal * input.boneWeights.z;
|
||||
localNormal += mat3x3f(m3[0].xyz, m3[1].xyz, m3[2].xyz) * inputNormal * input.boneWeights.w;
|
||||
|
||||
localTangent += mat3x3f(m0[0].xyz, m0[1].xyz, m0[2].xyz) * inputTangent * input.boneWeights.x;
|
||||
localTangent += mat3x3f(m1[0].xyz, m1[1].xyz, m1[2].xyz) * inputTangent * input.boneWeights.y;
|
||||
localTangent += mat3x3f(m2[0].xyz, m2[1].xyz, m2[2].xyz) * inputTangent * input.boneWeights.z;
|
||||
localTangent += mat3x3f(m3[0].xyz, m3[1].xyz, m3[2].xyz) * inputTangent * input.boneWeights.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
localPosition = inputPosition;
|
||||
localNormal = inputNormal;
|
||||
localTangent = inputTangent;
|
||||
}
|
||||
|
||||
var nm = mat3x3f(fyrox_instanceData.worldMatrix[0].xyz, fyrox_instanceData.worldMatrix[1].xyz, fyrox_instanceData.worldMatrix[2].xyz);
|
||||
output.normal = normalize(nm * localNormal);
|
||||
output.tangent = normalize(nm * localTangent);
|
||||
output.binormal = normalize(input.vertexTangent.w * cross(output.normal, output.tangent));
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
output.outPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz;
|
||||
output.secondTexCoord = input.vertexSecondTexCoord;
|
||||
|
||||
output.position = fyrox_instanceData.worldViewProjection * localPosition;
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
fragment_shader:
|
||||
r#"
|
||||
struct GBufferOutput {
|
||||
@location(0) outColor: vec4f,
|
||||
@location(1) outNormal: vec4f,
|
||||
@location(2) outAmbient: vec4f,
|
||||
@location(3) outMaterial: vec4f,
|
||||
@location(4) outDecalMask: u32,
|
||||
};
|
||||
|
||||
@fragment
|
||||
fn fs_main(
|
||||
@location(0) position: vec3f,
|
||||
@location(1) normal: vec3f,
|
||||
@location(2) texCoord: vec2f,
|
||||
@location(3) tangent: vec3f,
|
||||
@location(4) binormal: vec3f,
|
||||
@location(5) secondTexCoord: vec2f
|
||||
) -> GBufferOutput {
|
||||
var tangentSpace = mat3x3f(tangent, binormal, normal);
|
||||
var toFragment = normalize(position - fyrox_cameraData.position);
|
||||
|
||||
var tc: vec2f;
|
||||
if (fyrox_graphicsSettings.usePOM != 0u) {
|
||||
var toFragmentTangentSpace = normalize(transpose(tangentSpace) * toFragment);
|
||||
tc = S_ComputeParallaxTextureCoordinates(
|
||||
heightTexture_tex,
|
||||
heightTexture_samp,
|
||||
toFragmentTangentSpace,
|
||||
texCoord * properties.texCoordScale,
|
||||
properties.parallaxCenter,
|
||||
properties.parallaxScale
|
||||
);
|
||||
} else {
|
||||
tc = texCoord * properties.texCoordScale;
|
||||
}
|
||||
|
||||
var output: GBufferOutput;
|
||||
output.outColor = properties.diffuseColor * textureSample(diffuseTexture_tex, diffuseTexture_samp, tc);
|
||||
|
||||
// Alpha test.
|
||||
if (output.outColor.a < 0.5) {
|
||||
discard;
|
||||
}
|
||||
output.outColor.a = 1.0;
|
||||
|
||||
var n = normalize(textureSample(normalTexture_tex, normalTexture_samp, tc) * 2.0 - 1.0);
|
||||
output.outNormal = vec4f(normalize(tangentSpace * n.xyz) * 0.5 + 0.5, 1.0);
|
||||
|
||||
output.outMaterial.x = properties.metallicFactor * textureSample(metallicRoughnessTexture_tex, metallicRoughnessTexture_samp, tc).b; // Metallic
|
||||
output.outMaterial.y = properties.roughnessFactor * textureSample(metallicRoughnessTexture_tex, metallicRoughnessTexture_samp, tc).g; // Roughness
|
||||
output.outMaterial.z = textureSample(aoTexture_tex, aoTexture_samp, tc).r;
|
||||
output.outMaterial.a = 1.0;
|
||||
|
||||
output.outAmbient = vec4f(properties.emissionStrength * textureSample(emissionTexture_tex, emissionTexture_samp, tc).rgb + textureSample(lightmapTexture_tex, lightmapTexture_samp, secondTexCoord).rgb, 1.0);
|
||||
|
||||
output.outDecalMask = properties.layerIndex;
|
||||
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
),
|
||||
(
|
||||
name: "Forward",
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: Some(Back),
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: Some(Less),
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: SrcAlpha,
|
||||
dfactor: OneMinusSrcAlpha,
|
||||
alpha_sfactor: SrcAlpha,
|
||||
alpha_dfactor: OneMinusSrcAlpha,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
@location(2) vertexNormal: vec3f,
|
||||
@location(3) vertexTangent: vec4f,
|
||||
@location(4) boneWeights: vec4f,
|
||||
@location(5) boneIndices: vec4f,
|
||||
@location(6) vertexSecondTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) outPosition: vec3f,
|
||||
@location(1) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
|
||||
var localPosition = vec4f(0.0);
|
||||
|
||||
var inputPosition = vec4f(input.vertexPosition, 1.0);
|
||||
|
||||
for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) {
|
||||
var offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i);
|
||||
var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4];
|
||||
inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w);
|
||||
}
|
||||
|
||||
if (fyrox_instanceData.useSkeletalAnimation != 0u)
|
||||
{
|
||||
var i0 = i32(input.boneIndices.x);
|
||||
var i1 = i32(input.boneIndices.y);
|
||||
var i2 = i32(input.boneIndices.z);
|
||||
var i3 = i32(input.boneIndices.w);
|
||||
|
||||
var m0 = fyrox_boneMatrices.matrices[i0];
|
||||
var m1 = fyrox_boneMatrices.matrices[i1];
|
||||
var m2 = fyrox_boneMatrices.matrices[i2];
|
||||
var m3 = fyrox_boneMatrices.matrices[i3];
|
||||
|
||||
localPosition += m0 * inputPosition * input.boneWeights.x;
|
||||
localPosition += m1 * inputPosition * input.boneWeights.y;
|
||||
localPosition += m2 * inputPosition * input.boneWeights.z;
|
||||
localPosition += m3 * inputPosition * input.boneWeights.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
localPosition = inputPosition;
|
||||
}
|
||||
output.position = fyrox_instanceData.worldViewProjection * localPosition;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
output.outPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz;
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment
|
||||
fn fs_main(@location(0) position: vec3f, @location(1) texCoord: vec2f) -> @location(0) vec4f {
|
||||
return properties.diffuseColor * textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord);
|
||||
}
|
||||
"#,
|
||||
),
|
||||
(
|
||||
name: "DirectionalShadow",
|
||||
|
||||
draw_parameters: DrawParameters (
|
||||
cull_face: Some(Back),
|
||||
color_write: ColorMask(
|
||||
red: false,
|
||||
green: false,
|
||||
blue: false,
|
||||
alpha: false,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: Some(Less),
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
@location(2) vertexNormal: vec3f,
|
||||
@location(3) vertexTangent: vec4f,
|
||||
@location(4) boneWeights: vec4f,
|
||||
@location(5) boneIndices: vec4f,
|
||||
@location(6) vertexSecondTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
|
||||
var localPosition = vec4f(0.0);
|
||||
|
||||
var inputPosition = vec4f(input.vertexPosition, 1.0);
|
||||
|
||||
for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) {
|
||||
var offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i);
|
||||
var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4];
|
||||
inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w);
|
||||
}
|
||||
|
||||
if (fyrox_instanceData.useSkeletalAnimation != 0u)
|
||||
{
|
||||
var vertex = vec4f(input.vertexPosition, 1.0);
|
||||
|
||||
var m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)];
|
||||
var m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)];
|
||||
var m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)];
|
||||
var m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)];
|
||||
|
||||
localPosition += m0 * inputPosition * input.boneWeights.x;
|
||||
localPosition += m1 * inputPosition * input.boneWeights.y;
|
||||
localPosition += m2 * inputPosition * input.boneWeights.z;
|
||||
localPosition += m3 * inputPosition * input.boneWeights.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
localPosition = inputPosition;
|
||||
}
|
||||
|
||||
output.position = fyrox_instanceData.worldViewProjection * localPosition;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment
|
||||
fn fs_main(@location(0) texCoord: vec2f) {
|
||||
if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; }
|
||||
}
|
||||
"#,
|
||||
),
|
||||
(
|
||||
name: "SpotShadow",
|
||||
|
||||
draw_parameters: DrawParameters (
|
||||
cull_face: Some(Back),
|
||||
color_write: ColorMask(
|
||||
red: false,
|
||||
green: false,
|
||||
blue: false,
|
||||
alpha: false,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: Some(Less),
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
@location(2) vertexNormal: vec3f,
|
||||
@location(3) vertexTangent: vec4f,
|
||||
@location(4) boneWeights: vec4f,
|
||||
@location(5) boneIndices: vec4f,
|
||||
@location(6) vertexSecondTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
|
||||
var localPosition = vec4f(0.0);
|
||||
|
||||
var inputPosition = vec4f(input.vertexPosition, 1.0);
|
||||
|
||||
for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) {
|
||||
var offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i);
|
||||
var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4];
|
||||
inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w);
|
||||
}
|
||||
|
||||
if (fyrox_instanceData.useSkeletalAnimation != 0u)
|
||||
{
|
||||
var vertex = vec4f(input.vertexPosition, 1.0);
|
||||
|
||||
var m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)];
|
||||
var m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)];
|
||||
var m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)];
|
||||
var m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)];
|
||||
|
||||
localPosition += m0 * inputPosition * input.boneWeights.x;
|
||||
localPosition += m1 * inputPosition * input.boneWeights.y;
|
||||
localPosition += m2 * inputPosition * input.boneWeights.z;
|
||||
localPosition += m3 * inputPosition * input.boneWeights.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
localPosition = inputPosition;
|
||||
}
|
||||
|
||||
output.position = fyrox_instanceData.worldViewProjection * localPosition;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
@fragment
|
||||
fn fs_main(@location(0) texCoord: vec2f) {
|
||||
if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; }
|
||||
}
|
||||
"#,
|
||||
),
|
||||
(
|
||||
name: "PointShadow",
|
||||
|
||||
draw_parameters: DrawParameters (
|
||||
cull_face: Some(Back),
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: Some(Less),
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
scissor_box: None
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
struct VertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) vertexPosition: vec3f,
|
||||
@location(1) vertexTexCoord: vec2f,
|
||||
@location(2) vertexNormal: vec3f,
|
||||
@location(3) vertexTangent: vec4f,
|
||||
@location(4) boneWeights: vec4f,
|
||||
@location(5) boneIndices: vec4f,
|
||||
@location(6) vertexSecondTexCoord: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
@location(1) worldPosition: vec3f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
|
||||
var localPosition = vec4f(0.0);
|
||||
|
||||
var inputPosition = vec4f(input.vertexPosition, 1.0);
|
||||
|
||||
for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) {
|
||||
var offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i);
|
||||
var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4];
|
||||
inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w);
|
||||
}
|
||||
|
||||
if (fyrox_instanceData.useSkeletalAnimation != 0u)
|
||||
{
|
||||
var vertex = vec4f(input.vertexPosition, 1.0);
|
||||
|
||||
var m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)];
|
||||
var m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)];
|
||||
var m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)];
|
||||
var m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)];
|
||||
|
||||
localPosition += m0 * inputPosition * input.boneWeights.x;
|
||||
localPosition += m1 * inputPosition * input.boneWeights.y;
|
||||
localPosition += m2 * inputPosition * input.boneWeights.z;
|
||||
localPosition += m3 * inputPosition * input.boneWeights.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
localPosition = inputPosition;
|
||||
}
|
||||
|
||||
output.position = fyrox_instanceData.worldViewProjection * localPosition;
|
||||
output.worldPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz;
|
||||
output.texCoord = input.vertexTexCoord;
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
struct PointShadowOutput {
|
||||
@location(0) depth: f32,
|
||||
};
|
||||
|
||||
@fragment
|
||||
fn fs_main(@location(0) texCoord: vec2f, @location(1) worldPosition: vec3f) -> PointShadowOutput {
|
||||
if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; }
|
||||
var output: PointShadowOutput;
|
||||
output.depth = length(fyrox_lightData.lightPosition - worldPosition);
|
||||
return output;
|
||||
}
|
||||
"#,
|
||||
)
|
||||
],
|
||||
)
|
||||
@@ -28,3 +28,7 @@ regex = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
fyrox-texture = { version = "2.0.0-rc.1", path = "../fyrox-texture" }
|
||||
|
||||
[features]
|
||||
backend_opengl = []
|
||||
backend_wgpu = []
|
||||
|
||||
@@ -966,11 +966,25 @@ impl ShaderResourceExtension for ShaderResource {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "backend_opengl"))]
|
||||
macro_rules! embedded_shader {
|
||||
($file:literal) => {
|
||||
embedded_data_source!(concat!("standard/wgpu/", $file))
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_opengl")]
|
||||
macro_rules! embedded_shader {
|
||||
($file:literal) => {
|
||||
embedded_data_source!(concat!("standard/opengl/", $file))
|
||||
};
|
||||
}
|
||||
|
||||
/// Standard shader.
|
||||
pub static STANDARD: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|| {
|
||||
BuiltInResource::new(
|
||||
STANDARD_SHADER_NAME,
|
||||
embedded_data_source!("standard/standard.shader"),
|
||||
embedded_shader!("standard.shader"),
|
||||
|data| {
|
||||
ShaderResource::new_ok(
|
||||
uuid!("87195f6e-cba4-4c27-9f89-d0bf726db965"),
|
||||
@@ -985,7 +999,7 @@ pub static STANDARD: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|| {
|
||||
pub static STANDARD_2D: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|| {
|
||||
BuiltInResource::new(
|
||||
STANDARD_2D_SHADER_NAME,
|
||||
embedded_data_source!("standard/standard2d.shader"),
|
||||
embedded_shader!("standard2d.shader"),
|
||||
|data| {
|
||||
ShaderResource::new_ok(
|
||||
uuid!("55fa05b0-3c25-4e46-bae7-65f093185b75"),
|
||||
@@ -1000,7 +1014,7 @@ pub static STANDARD_2D: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|| {
|
||||
pub static STANDARD_PARTICLE_SYSTEM: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|| {
|
||||
BuiltInResource::new(
|
||||
STANDARD_PARTICLE_SYSTEM_SHADER_NAME,
|
||||
embedded_data_source!("standard/standard_particle_system.shader"),
|
||||
embedded_shader!("standard_particle_system.shader"),
|
||||
|data| {
|
||||
ShaderResource::new_ok(
|
||||
uuid!("eb474445-6a25-4481-bca9-f919699c300f"),
|
||||
@@ -1015,7 +1029,7 @@ pub static STANDARD_PARTICLE_SYSTEM: LazyLock<BuiltInResource<Shader>> = LazyLoc
|
||||
pub static STANDARD_SPRITE: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|| {
|
||||
BuiltInResource::new(
|
||||
STANDARD_SPRITE_SHADER_NAME,
|
||||
embedded_data_source!("standard/standard_sprite.shader"),
|
||||
embedded_shader!("standard_sprite.shader"),
|
||||
|data| {
|
||||
ShaderResource::new_ok(
|
||||
uuid!("a135826a-4c1b-46d5-ba1f-0c9a226aa52c"),
|
||||
@@ -1030,7 +1044,7 @@ pub static STANDARD_SPRITE: LazyLock<BuiltInResource<Shader>> = LazyLock::new(||
|
||||
pub static STANDARD_TERRAIN: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|| {
|
||||
BuiltInResource::new(
|
||||
STANDARD_TERRAIN_SHADER_NAME,
|
||||
embedded_data_source!("standard/terrain.shader"),
|
||||
embedded_shader!("terrain.shader"),
|
||||
|data| {
|
||||
ShaderResource::new_ok(
|
||||
uuid!("4911aafe-9bb1-4115-a958-25b57b87b51e"),
|
||||
@@ -1045,7 +1059,7 @@ pub static STANDARD_TERRAIN: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|
|
||||
pub static STANDARD_TILE: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|| {
|
||||
BuiltInResource::new(
|
||||
STANDARD_TILE_SHADER_NAME,
|
||||
embedded_data_source!("standard/tile.shader"),
|
||||
embedded_shader!("tile.shader"),
|
||||
|data| {
|
||||
ShaderResource::new_ok(
|
||||
uuid!("5f29dd3a-ea99-480c-bb02-d2c6420843b1"),
|
||||
@@ -1060,7 +1074,7 @@ pub static STANDARD_TILE: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|| {
|
||||
pub static STANDARD_TWOSIDES: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|| {
|
||||
BuiltInResource::new(
|
||||
STANDARD_TWOSIDES_SHADER_NAME,
|
||||
embedded_data_source!("standard/standard-two-sides.shader"),
|
||||
embedded_shader!("standard-two-sides.shader"),
|
||||
|data| {
|
||||
ShaderResource::new_ok(
|
||||
uuid!("f7979409-5185-4e1c-a644-d53cea64af8f"),
|
||||
@@ -1075,7 +1089,7 @@ pub static STANDARD_TWOSIDES: LazyLock<BuiltInResource<Shader>> = LazyLock::new(
|
||||
pub static STANDARD_WIDGET: LazyLock<BuiltInResource<Shader>> = LazyLock::new(|| {
|
||||
BuiltInResource::new(
|
||||
STANDARD_WIDGET_SHADER_NAME,
|
||||
embedded_data_source!("standard/widget.shader"),
|
||||
embedded_shader!("widget.shader"),
|
||||
|data| {
|
||||
ShaderResource::new_ok(
|
||||
uuid!("f5908aa4-e187-42a8-95d2-dc6577f6def4"),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user