mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'imbris/pixel-perfection-v2' into 'master'
Move image scaling into the UI shaders rather than precomputing it on the CPU Closes #257 See merge request veloren/veloren!3573
This commit is contained in:
commit
54c39c03f7
@ -32,6 +32,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Climbing no longer requires having 10 energy
|
- Climbing no longer requires having 10 energy
|
||||||
- Castles will now be placed close to towns
|
- Castles will now be placed close to towns
|
||||||
- Sword
|
- Sword
|
||||||
|
- Rescaling of images for the UI is now done when sampling from them on the GPU. Improvements are
|
||||||
|
particularily noticeable when opening the map screen (which involves rescaling a few large
|
||||||
|
images) and also when using the voxel minimap view (where a medium size image is updated often).
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
BIN
assets/voxygen/element/ui/generic/frames/banner_bot.png
(Stored with Git LFS)
BIN
assets/voxygen/element/ui/generic/frames/banner_bot.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/ui/generic/frames/esc_menu.png
(Stored with Git LFS)
BIN
assets/voxygen/element/ui/generic/frames/esc_menu.png
(Stored with Git LFS)
Binary file not shown.
17
assets/voxygen/shaders/premultiply-alpha-frag.glsl
Normal file
17
assets/voxygen/shaders/premultiply-alpha-frag.glsl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#version 420 core
|
||||||
|
#extension GL_EXT_samplerless_texture_functions : enable
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0)
|
||||||
|
uniform texture2D source_texture;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 source_coords;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 target_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// We get free nonlinear -> linear conversion when sampling from srgb texture;
|
||||||
|
vec4 linear = texelFetch(source_texture, ivec2(source_coords), 0);
|
||||||
|
vec4 premultiplied_linear = vec4(linear.rgb * linear.a, linear.a);
|
||||||
|
// We get free linear -> nonlinear conversion rendering to srgb texture.
|
||||||
|
target_color = premultiplied_linear;
|
||||||
|
}
|
48
assets/voxygen/shaders/premultiply-alpha-vert.glsl
Normal file
48
assets/voxygen/shaders/premultiply-alpha-vert.glsl
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#version 420 core
|
||||||
|
|
||||||
|
layout(push_constant) uniform Params {
|
||||||
|
// Size of the source image.
|
||||||
|
uint source_size_xy;
|
||||||
|
// Offset to place the image at in the target texture.
|
||||||
|
//
|
||||||
|
// Origin is the top-left.
|
||||||
|
uint target_offset_xy;
|
||||||
|
// Size of the target texture.
|
||||||
|
uint target_size_xy;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(location = 0) out vec2 source_coords;
|
||||||
|
|
||||||
|
uvec2 unpack(uint xy) {
|
||||||
|
return uvec2(
|
||||||
|
bitfieldExtract(xy, 0, 16),
|
||||||
|
bitfieldExtract(xy, 16, 16)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 source_size = vec2(unpack(source_size_xy));
|
||||||
|
vec2 target_offset = vec2(unpack(target_offset_xy));
|
||||||
|
vec2 target_size = vec2(unpack(target_size_xy));
|
||||||
|
|
||||||
|
// Generate rectangle (counter clockwise triangles)
|
||||||
|
//
|
||||||
|
// 0 0 1 1 1 0
|
||||||
|
float x_select = float(((uint(gl_VertexIndex) + 1u) / 3u) % 2u);
|
||||||
|
// 1 0 0 0 1 1
|
||||||
|
float y_select = float(((uint(gl_VertexIndex) + 5u) / 3u) % 2u);
|
||||||
|
|
||||||
|
source_coords = vec2(
|
||||||
|
// left -> right (on screen)
|
||||||
|
mix(0.0, 1.0, x_select),
|
||||||
|
// bottom -> top (on screen)
|
||||||
|
mix(1.0, 0.0, y_select)
|
||||||
|
) * source_size;
|
||||||
|
|
||||||
|
vec2 target_coords_normalized = (target_offset + source_coords) / target_size;
|
||||||
|
|
||||||
|
// Flip y and transform [0.0, 1.0] -> [-1.0, 1.0] to get NDC coordinates.
|
||||||
|
vec2 v_pos = ((target_coords_normalized * 2.0) - vec2(1.0)) * vec2(1.0, -1.0);
|
||||||
|
|
||||||
|
gl_Position = vec4(v_pos, 0.0, 1.0);
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
#version 420 core
|
#version 420 core
|
||||||
|
|
||||||
#include <globals.glsl>
|
#include <globals.glsl>
|
||||||
|
#include <constants.glsl>
|
||||||
|
|
||||||
layout(location = 0) in vec2 f_uv;
|
layout(location = 0) in vec2 f_uv;
|
||||||
layout(location = 1) in vec4 f_color;
|
layout(location = 1) in vec4 f_color;
|
||||||
layout(location = 2) flat in uint f_mode;
|
layout(location = 2) flat in vec2 f_scale;
|
||||||
|
layout(location = 3) flat in uint f_mode;
|
||||||
|
|
||||||
layout (std140, set = 1, binding = 0)
|
layout (std140, set = 1, binding = 0)
|
||||||
uniform u_locals {
|
uniform u_locals {
|
||||||
@ -15,17 +17,197 @@ layout(set = 2, binding = 0)
|
|||||||
uniform texture2D t_tex;
|
uniform texture2D t_tex;
|
||||||
layout(set = 2, binding = 1)
|
layout(set = 2, binding = 1)
|
||||||
uniform sampler s_tex;
|
uniform sampler s_tex;
|
||||||
|
layout (std140, set = 2, binding = 2)
|
||||||
|
uniform tex_locals {
|
||||||
|
uvec2 texture_size;
|
||||||
|
};
|
||||||
|
|
||||||
layout(location = 0) out vec4 tgt_color;
|
layout(location = 0) out vec4 tgt_color;
|
||||||
|
|
||||||
|
// Adjusts the provided uv value to account for coverage of pixels from the
|
||||||
|
// sampled texture by the current fragment when upscaling.
|
||||||
|
//
|
||||||
|
// * `pos` - Position in the sampled texture in pixel coordinates. This is
|
||||||
|
// where the center of the current fragment lies on the sampled texture.
|
||||||
|
// * `scale` - Scaling of pixels from the sampled texture to the render target.
|
||||||
|
// This is the amount of fragments that each pixel from the sampled texture
|
||||||
|
// covers.
|
||||||
|
float upscale_adjust(float pos, float scale) {
|
||||||
|
// To retain crisp borders of upscaled pixel art, images are upscaled
|
||||||
|
// following the algorithm outlined here:
|
||||||
|
//
|
||||||
|
// https://csantosbh.wordpress.com/2014/01/25/manual-texture-filtering-for-pixelated-games-in-webgl/
|
||||||
|
//
|
||||||
|
// `min(x * scale, 0.5) + max((x - 1.0) * scale, 0.0)`
|
||||||
|
//
|
||||||
|
float frac = fract(pos);
|
||||||
|
// Right of nearest pixel in the sampled texture.
|
||||||
|
float base = floor(pos);
|
||||||
|
// This will be 0.5 when the current fragment lies entirely inside a pixel
|
||||||
|
// in the sampled texture.
|
||||||
|
float adjustment = min(frac * scale, 0.5) + max((frac - 1.0) * scale + 0.5, 0.0);
|
||||||
|
return base + adjustment;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes info needed for downscaling using two samples in a single
|
||||||
|
// dimension. This info includes the two position to sample at (called
|
||||||
|
// `offsets` even though they aren't actually offsets from the supplied
|
||||||
|
// position) and the `weights` to multiply each of those samples by before
|
||||||
|
// combining them.
|
||||||
|
//
|
||||||
|
// See `upscale_adjust` for semantics of `pos` and `scale` parameters.
|
||||||
|
//
|
||||||
|
// Ouput via `weights` and `offsets` parameters.
|
||||||
|
void downscale_params(float pos, float scale, out vec2 weights, out vec2 offsets) {
|
||||||
|
// For `scale` 0.33333..1.0 we round to the nearest pixel edge and split
|
||||||
|
// there. We compute the length of each side. Then the sampling point is
|
||||||
|
// computed as this distance from the split point via this formula where
|
||||||
|
// `l` is the length of that side of split:
|
||||||
|
//
|
||||||
|
// `1.5 - (1.0 / max(l, 1.0))`
|
||||||
|
//
|
||||||
|
// For `scale` ..0.3333 the current fragment can potentially span more than
|
||||||
|
// 4 pixels (within a single dimension) in the sampled texture. So we can't
|
||||||
|
// perfectly compute the contribution of each covered pixel in the sampled
|
||||||
|
// texture with only 2 samples (along each dimension). Thus, we fallback to
|
||||||
|
// an imperfect technique of just sampling 1 pixel length from the center
|
||||||
|
// on each side of the nearest pixel edge. An alternative might be to
|
||||||
|
// pre-compute mipmap levels that could be sampled from, although this
|
||||||
|
// could interact poorly with the atlas.
|
||||||
|
if (scale > (1.0 / 3.0)) {
|
||||||
|
// Width of the fragment in terms of pixels in the sampled texture.
|
||||||
|
float width = 1.0 / scale;
|
||||||
|
// Right side of the fragment in the sampled texture.
|
||||||
|
float right = pos - width / 2.0;
|
||||||
|
float split = round(pos);
|
||||||
|
float right_len = split - right;
|
||||||
|
float left_len = width - right_len;
|
||||||
|
float right_sample_offset = 1.5 - (1.0 / max(right_len, 1.0));
|
||||||
|
float left_sample_offset = 1.5 - (1.0 / max(left_len, 1.0));
|
||||||
|
offsets = vec2(split) + vec2(-right_sample_offset, left_sample_offset);
|
||||||
|
weights = vec2(right_len, left_len) / width;
|
||||||
|
} else {
|
||||||
|
offsets = round(pos) + vec2(-1.0, 1.0);
|
||||||
|
// We split in the middle so weights for both sides are the same.
|
||||||
|
weights = vec2(0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 sample
|
||||||
|
vec4 upscale_xy(vec2 uv_pixel, vec2 scale) {
|
||||||
|
// When slowly panning something (e.g. the map), a very small amount of
|
||||||
|
// wobbling is still observable (not as much as nearest sampling). It
|
||||||
|
// is possible to eliminate this by making the edges slightly blurry by
|
||||||
|
// lowering the scale a bit here. However, this does make edges little
|
||||||
|
// less crisp and can cause bleeding in from other images packed into
|
||||||
|
// the atlas in the current setup.
|
||||||
|
vec2 adjusted = vec2(upscale_adjust(uv_pixel.x, scale.x), upscale_adjust(uv_pixel.y, scale.y));
|
||||||
|
// Convert back to 0.0..1.0 by dividing by texture size.
|
||||||
|
vec2 uv = adjusted / texture_size;
|
||||||
|
return textureLod(sampler2D(t_tex, s_tex), uv, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 samples
|
||||||
|
vec4 upscale_x_downscale_y(vec2 uv_pixel, vec2 scale) {
|
||||||
|
float x_adjusted = upscale_adjust(uv_pixel.x, scale.x);
|
||||||
|
vec2 weights, offsets;
|
||||||
|
downscale_params(uv_pixel.y, scale.y, weights, offsets);
|
||||||
|
vec2 uv0 = vec2(x_adjusted, offsets[0]) / texture_size;
|
||||||
|
vec2 uv1 = vec2(x_adjusted, offsets[1]) / texture_size;
|
||||||
|
vec4 s0 = textureLod(sampler2D(t_tex, s_tex), uv0, 0);
|
||||||
|
vec4 s1 = textureLod(sampler2D(t_tex, s_tex), uv1, 0);
|
||||||
|
return s0 * weights[0] + s1 * weights[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 samples
|
||||||
|
vec4 downscale_x_upscale_y(vec2 uv_pixel, vec2 scale) {
|
||||||
|
float y_adjusted = upscale_adjust(uv_pixel.y, scale.y);
|
||||||
|
vec2 weights, offsets;
|
||||||
|
downscale_params(uv_pixel.x, scale.x, weights, offsets);
|
||||||
|
vec2 uv0 = vec2(offsets[0], y_adjusted) / texture_size;
|
||||||
|
vec2 uv1 = vec2(offsets[1], y_adjusted) / texture_size;
|
||||||
|
vec4 s0 = textureLod(sampler2D(t_tex, s_tex), uv0, 0);
|
||||||
|
vec4 s1 = textureLod(sampler2D(t_tex, s_tex), uv1, 0);
|
||||||
|
return s0 * weights[0] + s1 * weights[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 samples
|
||||||
|
vec4 downscale_xy(vec2 uv_pixel, vec2 scale) {
|
||||||
|
vec2 weights_x, offsets_x, weights_y, offsets_y;
|
||||||
|
downscale_params(uv_pixel.x, scale.x, weights_x, offsets_x);
|
||||||
|
downscale_params(uv_pixel.y, scale.y, weights_y, offsets_y);
|
||||||
|
vec2 uv0 = vec2(offsets_x[0], offsets_y[0]) / texture_size;
|
||||||
|
vec2 uv1 = vec2(offsets_x[1], offsets_y[0]) / texture_size;
|
||||||
|
vec2 uv2 = vec2(offsets_x[0], offsets_y[1]) / texture_size;
|
||||||
|
vec2 uv3 = vec2(offsets_x[1], offsets_y[1]) / texture_size;
|
||||||
|
vec4 s0 = textureLod(sampler2D(t_tex, s_tex), uv0, 0);
|
||||||
|
vec4 s1 = textureLod(sampler2D(t_tex, s_tex), uv1, 0);
|
||||||
|
vec4 s2 = textureLod(sampler2D(t_tex, s_tex), uv2, 0);
|
||||||
|
vec4 s3 = textureLod(sampler2D(t_tex, s_tex), uv3, 0);
|
||||||
|
vec4 s01 = s0 * weights_x[0] + s1 * weights_x[1];
|
||||||
|
vec4 s23 = s2 * weights_x[0] + s3 * weights_x[1];
|
||||||
|
// Useful to visualize things below the limit where downscaling is supposed
|
||||||
|
// to be perfectly accurate.
|
||||||
|
/*if (scale.x < (1.0 / 3.0)) {
|
||||||
|
return vec4(1, 0, 0, 1);
|
||||||
|
}*/
|
||||||
|
return s01 * weights_y[0] + s23 * weights_y[1];
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// Text
|
// Text
|
||||||
if (f_mode == uint(0)) {
|
if (f_mode == uint(0)) {
|
||||||
tgt_color = f_color * vec4(1.0, 1.0, 1.0, textureLod(sampler2D(t_tex, s_tex), f_uv, 0).a);
|
// NOTE: This now uses linear filter since all `Texture::new_dynamic`
|
||||||
|
// was changed to this by default. Glyphs are usually rasterized to be
|
||||||
|
// pretty close to the target size (so the filter change may have no
|
||||||
|
// effect), but there are thresholds within which the same rasterized
|
||||||
|
// glyph will be re-used. I wasn't able to observe any differences.
|
||||||
|
vec2 uv = f_uv;
|
||||||
|
#ifdef EXPERIMENTAL_UINEARESTSCALING
|
||||||
|
uv = (floor(uv * texture_size) + 0.5) / texture_size;
|
||||||
|
#endif
|
||||||
|
tgt_color = f_color * vec4(1.0, 1.0, 1.0, textureLod(sampler2D(t_tex, s_tex), uv, 0).a);
|
||||||
// Image
|
// Image
|
||||||
// HACK: bit 0 is set for both ordinary and north-facing images.
|
// HACK: bit 0 is set for both ordinary and north-facing images.
|
||||||
} else if ((f_mode & uint(1)) == uint(1)) {
|
} else if ((f_mode & uint(1)) == uint(1)) {
|
||||||
tgt_color = f_color * textureLod(sampler2D(t_tex, s_tex), f_uv, 0);
|
// NOTE: We don't have to account for bleeding over the border of an image
|
||||||
|
// due to how the ui currently handles rendering images. Currently, any
|
||||||
|
// edges of an image being rendered that don't line up with a pixel are
|
||||||
|
// snapped to a pixel, so we will never render any pixels containing an
|
||||||
|
// image that lie partly outside that image (and thus the sampling here
|
||||||
|
// will never try to sample outside an image). So we don't have to
|
||||||
|
// worry about bleeding in the atlas and/or what the border behavior
|
||||||
|
// should be.
|
||||||
|
|
||||||
|
// Convert to sampled pixel coordinates.
|
||||||
|
vec2 uv_pixel = f_uv * texture_size;
|
||||||
|
vec4 image_color;
|
||||||
|
#ifdef EXPERIMENTAL_UINEARESTSCALING
|
||||||
|
vec2 uv = (floor(uv_pixel) + 0.5) / texture_size;
|
||||||
|
image_color = textureLod(sampler2D(t_tex, s_tex), uv, 0);
|
||||||
|
#else
|
||||||
|
if (f_scale.x >= 1.0) {
|
||||||
|
if (f_scale.y >= 1.0) {
|
||||||
|
image_color = upscale_xy(uv_pixel, f_scale);
|
||||||
|
} else {
|
||||||
|
image_color = upscale_x_downscale_y(uv_pixel, f_scale);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (f_scale.y >= 1.0) {
|
||||||
|
image_color = downscale_x_upscale_y(uv_pixel, f_scale);
|
||||||
|
} else {
|
||||||
|
image_color = downscale_xy(uv_pixel, f_scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// un-premultiply alpha (linear filtering above requires alpha to be
|
||||||
|
// pre-multiplied)
|
||||||
|
if (image_color.a > 0.001) {
|
||||||
|
image_color.rgb /= image_color.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
tgt_color = f_color * image_color;
|
||||||
// 2D Geometry
|
// 2D Geometry
|
||||||
} else if (f_mode == uint(2)) {
|
} else if (f_mode == uint(2)) {
|
||||||
tgt_color = f_color;
|
tgt_color = f_color;
|
||||||
|
@ -6,7 +6,8 @@ layout(location = 0) in vec2 v_pos;
|
|||||||
layout(location = 1) in vec2 v_uv;
|
layout(location = 1) in vec2 v_uv;
|
||||||
layout(location = 2) in vec4 v_color;
|
layout(location = 2) in vec4 v_color;
|
||||||
layout(location = 3) in vec2 v_center;
|
layout(location = 3) in vec2 v_center;
|
||||||
layout(location = 4) in uint v_mode;
|
layout(location = 4) in vec2 v_scale;
|
||||||
|
layout(location = 5) in uint v_mode;
|
||||||
|
|
||||||
layout (std140, set = 1, binding = 0)
|
layout (std140, set = 1, binding = 0)
|
||||||
uniform u_locals {
|
uniform u_locals {
|
||||||
@ -17,10 +18,15 @@ layout(set = 2, binding = 0)
|
|||||||
uniform texture2D t_tex;
|
uniform texture2D t_tex;
|
||||||
layout(set = 2, binding = 1)
|
layout(set = 2, binding = 1)
|
||||||
uniform sampler s_tex;
|
uniform sampler s_tex;
|
||||||
|
layout (std140, set = 2, binding = 2)
|
||||||
|
uniform tex_locals {
|
||||||
|
uvec2 texture_size;
|
||||||
|
};
|
||||||
|
|
||||||
layout(location = 0) out vec2 f_uv;
|
layout(location = 0) out vec2 f_uv;
|
||||||
layout(location = 1) out vec4 f_color;
|
layout(location = 1) out vec4 f_color;
|
||||||
layout(location = 2) flat out uint f_mode;
|
layout(location = 2) flat out vec2 f_scale;
|
||||||
|
layout(location = 3) flat out uint f_mode;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
f_color = v_color;
|
f_color = v_color;
|
||||||
@ -39,7 +45,7 @@ void main() {
|
|||||||
gl_Position = vec4(v_pos, 0.5, 1.0);
|
gl_Position = vec4(v_pos, 0.5, 1.0);
|
||||||
vec2 look_at_dir = normalize(vec2(-view_mat[0][2], -view_mat[1][2]));
|
vec2 look_at_dir = normalize(vec2(-view_mat[0][2], -view_mat[1][2]));
|
||||||
// TODO: Consider cleaning up matrix to something more efficient (e.g. a mat3).
|
// TODO: Consider cleaning up matrix to something more efficient (e.g. a mat3).
|
||||||
vec2 aspect_ratio = textureSize(sampler2D(t_tex, s_tex), 0).yx;
|
vec2 aspect_ratio = texture_size.yx;
|
||||||
mat2 look_at = mat2(look_at_dir.y, look_at_dir.x, -look_at_dir.x, look_at_dir.y);
|
mat2 look_at = mat2(look_at_dir.y, look_at_dir.x, -look_at_dir.x, look_at_dir.y);
|
||||||
vec2 v_centered = (v_uv - v_center) / aspect_ratio;
|
vec2 v_centered = (v_uv - v_center) / aspect_ratio;
|
||||||
vec2 v_rotated = look_at * v_centered;
|
vec2 v_rotated = look_at * v_centered;
|
||||||
@ -60,5 +66,6 @@ void main() {
|
|||||||
gl_Position = vec4(v_pos, 0.5, 1.0);
|
gl_Position = vec4(v_pos, 0.5, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f_scale = v_scale;
|
||||||
f_mode = v_mode;
|
f_mode = v_mode;
|
||||||
}
|
}
|
||||||
|
@ -2782,11 +2782,13 @@ impl Hud {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (i, timing) in gpu_timings.iter().enumerate() {
|
for (i, timing) in gpu_timings.iter().enumerate() {
|
||||||
let timings_text = &format!(
|
let label = timing.1;
|
||||||
"{:16}{:.3} ms",
|
// We skip displaying these since they aren't present every frame.
|
||||||
&format!("{}:", timing.1),
|
if label.starts_with(crate::render::UI_PREMULTIPLY_PASS) {
|
||||||
timing.2 * 1000.0,
|
continue;
|
||||||
);
|
}
|
||||||
|
let timings_text =
|
||||||
|
&format!("{:16}{:.3} ms", &format!("{label}:"), timing.2 * 1000.0,);
|
||||||
let timings_widget = Text::new(timings_text)
|
let timings_widget = Text::new(timings_text)
|
||||||
.color(TEXT_COLOR)
|
.color(TEXT_COLOR)
|
||||||
.down(V_PAD)
|
.down(V_PAD)
|
||||||
|
@ -155,7 +155,7 @@ pub trait PlayState {
|
|||||||
fn globals_bind_group(&self) -> &GlobalsBindGroup;
|
fn globals_bind_group(&self) -> &GlobalsBindGroup;
|
||||||
|
|
||||||
/// Draw the play state.
|
/// Draw the play state.
|
||||||
fn render<'a>(&'a self, drawer: &mut Drawer<'a>, settings: &Settings);
|
fn render(&self, drawer: &mut Drawer<'_>, settings: &Settings);
|
||||||
|
|
||||||
/// Determines whether egui will be rendered for this play state
|
/// Determines whether egui will be rendered for this play state
|
||||||
fn egui_enabled(&self) -> bool;
|
fn egui_enabled(&self) -> bool;
|
||||||
|
@ -275,7 +275,7 @@ impl PlayState for CharSelectionState {
|
|||||||
|
|
||||||
fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
|
fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
|
||||||
|
|
||||||
fn render<'a>(&'a self, drawer: &mut Drawer<'a>, _: &Settings) {
|
fn render(&self, drawer: &mut Drawer<'_>, _: &Settings) {
|
||||||
let client = self.client.borrow();
|
let client = self.client.borrow();
|
||||||
let (humanoid_body, loadout) =
|
let (humanoid_body, loadout) =
|
||||||
Self::get_humanoid_body_inventory(&self.char_selection_ui, &client);
|
Self::get_humanoid_body_inventory(&self.char_selection_ui, &client);
|
||||||
|
@ -394,7 +394,7 @@ impl PlayState for MainMenuState {
|
|||||||
|
|
||||||
fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
|
fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
|
||||||
|
|
||||||
fn render<'a>(&'a self, drawer: &mut Drawer<'a>, _: &Settings) {
|
fn render(&self, drawer: &mut Drawer<'_>, _: &Settings) {
|
||||||
// Draw the UI to the screen.
|
// Draw the UI to the screen.
|
||||||
let mut third_pass = drawer.third_pass();
|
let mut third_pass = drawer.third_pass();
|
||||||
if let Some(mut ui_drawer) = third_pass.draw_ui() {
|
if let Some(mut ui_drawer) = third_pass.draw_ui() {
|
||||||
|
@ -43,7 +43,8 @@ pub use self::{
|
|||||||
create_quad as create_ui_quad,
|
create_quad as create_ui_quad,
|
||||||
create_quad_vert_gradient as create_ui_quad_vert_gradient, create_tri as create_ui_tri,
|
create_quad_vert_gradient as create_ui_quad_vert_gradient, create_tri as create_ui_tri,
|
||||||
BoundLocals as UiBoundLocals, Locals as UiLocals, Mode as UiMode,
|
BoundLocals as UiBoundLocals, Locals as UiLocals, Mode as UiMode,
|
||||||
TextureBindGroup as UiTextureBindGroup, Vertex as UiVertex,
|
TextureBindGroup as UiTextureBindGroup, UploadBatchId as UiUploadBatchId,
|
||||||
|
Vertex as UiVertex,
|
||||||
},
|
},
|
||||||
GlobalModel, Globals, GlobalsBindGroup, GlobalsLayouts, Light, Shadow,
|
GlobalModel, Globals, GlobalsBindGroup, GlobalsLayouts, Light, Shadow,
|
||||||
},
|
},
|
||||||
@ -52,7 +53,7 @@ pub use self::{
|
|||||||
DebugDrawer, DebugShadowDrawer, Drawer, FigureDrawer, FigureShadowDrawer,
|
DebugDrawer, DebugShadowDrawer, Drawer, FigureDrawer, FigureShadowDrawer,
|
||||||
FirstPassDrawer, ParticleDrawer, PreparedUiDrawer, ShadowPassDrawer, SpriteDrawer,
|
FirstPassDrawer, ParticleDrawer, PreparedUiDrawer, ShadowPassDrawer, SpriteDrawer,
|
||||||
TerrainDrawer, TerrainShadowDrawer, ThirdPassDrawer, TrailDrawer,
|
TerrainDrawer, TerrainShadowDrawer, ThirdPassDrawer, TrailDrawer,
|
||||||
TransparentPassDrawer, UiDrawer, VolumetricPassDrawer,
|
TransparentPassDrawer, UiDrawer, VolumetricPassDrawer, UI_PREMULTIPLY_PASS,
|
||||||
},
|
},
|
||||||
AltIndices, ColLightInfo, CullingMode, Renderer,
|
AltIndices, ColLightInfo, CullingMode, Renderer,
|
||||||
},
|
},
|
||||||
@ -536,4 +537,7 @@ pub enum ExperimentalShader {
|
|||||||
SmearReflections,
|
SmearReflections,
|
||||||
/// Apply the point shadows from cheap shadows on top of shadow mapping.
|
/// Apply the point shadows from cheap shadows on top of shadow mapping.
|
||||||
PointShadowsWithShadowMapping,
|
PointShadowsWithShadowMapping,
|
||||||
|
/// Make the UI uses nearest neighbor filtering for scaling images instead
|
||||||
|
/// of trying to filter based on the coverage of the sampled pixels.
|
||||||
|
UiNearestScaling,
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,19 @@
|
|||||||
use super::super::{Bound, Consts, GlobalsLayouts, Quad, Texture, Tri, Vertex as VertexTrait};
|
use super::super::{Bound, Consts, GlobalsLayouts, Quad, Texture, Tri, Vertex as VertexTrait};
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use core::num::NonZeroU32;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
/// The format of textures that the UI sources image data from.
|
||||||
|
///
|
||||||
|
/// Note, the is not directly used in all relevant locations, but still helps to
|
||||||
|
/// more clearly document the that this is the format being used. Notably,
|
||||||
|
/// textures are created via `renderer.create_dynamic_texture(...)` and
|
||||||
|
/// `renderer.create_texture(&DynamicImage::ImageRgba(image), ...)` (TODO:
|
||||||
|
/// update if we have to refactor when implementing the RENDER_ATTACHMENT
|
||||||
|
/// usage).
|
||||||
|
const UI_IMAGE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
||||||
pub struct Vertex {
|
pub struct Vertex {
|
||||||
@ -10,12 +21,17 @@ pub struct Vertex {
|
|||||||
uv: [f32; 2],
|
uv: [f32; 2],
|
||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
center: [f32; 2],
|
center: [f32; 2],
|
||||||
|
// Used calculating where to sample scaled images.
|
||||||
|
scale: [f32; 2],
|
||||||
mode: u32,
|
mode: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vertex {
|
impl Vertex {
|
||||||
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||||
const ATTRIBUTES: [wgpu::VertexAttribute; 5] = wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Float32x4, 3 => Float32x2, 4 => Uint32];
|
const ATTRIBUTES: [wgpu::VertexAttribute; 6] = wgpu::vertex_attr_array![
|
||||||
|
0 => Float32x2, 1 => Float32x2, 2 => Float32x4,
|
||||||
|
3 => Float32x2, 4 => Float32x2, 5 => Uint32,
|
||||||
|
];
|
||||||
wgpu::VertexBufferLayout {
|
wgpu::VertexBufferLayout {
|
||||||
array_stride: Self::STRIDE,
|
array_stride: Self::STRIDE,
|
||||||
step_mode: wgpu::InputStepMode::Vertex,
|
step_mode: wgpu::InputStepMode::Vertex,
|
||||||
@ -47,6 +63,20 @@ impl Default for Locals {
|
|||||||
fn default() -> Self { Self { pos: [0.0; 4] } }
|
fn default() -> Self { Self { pos: [0.0; 4] } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
||||||
|
pub struct TexLocals {
|
||||||
|
texture_size: [u32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec2<u32>> for TexLocals {
|
||||||
|
fn from(texture_size: Vec2<u32>) -> Self {
|
||||||
|
Self {
|
||||||
|
texture_size: texture_size.into_array(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw text from the text cache texture `tex` in the fragment shader.
|
/// Draw text from the text cache texture `tex` in the fragment shader.
|
||||||
pub const MODE_TEXT: u32 = 0;
|
pub const MODE_TEXT: u32 = 0;
|
||||||
/// Draw an image from the texture at `tex` in the fragment shader.
|
/// Draw an image from the texture at `tex` in the fragment shader.
|
||||||
@ -64,22 +94,44 @@ pub const MODE_IMAGE_SOURCE_NORTH: u32 = 3;
|
|||||||
/// FIXME: Make more principled.
|
/// FIXME: Make more principled.
|
||||||
pub const MODE_IMAGE_TARGET_NORTH: u32 = 5;
|
pub const MODE_IMAGE_TARGET_NORTH: u32 = 5;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Text,
|
Text,
|
||||||
Image,
|
Image {
|
||||||
|
scale: Vec2<f32>,
|
||||||
|
},
|
||||||
Geometry,
|
Geometry,
|
||||||
ImageSourceNorth,
|
/// Draw an image from the texture at `tex` in the fragment shader, with the
|
||||||
ImageTargetNorth,
|
/// source rectangle rotated to face north (TODO: detail on what "north"
|
||||||
|
/// means here).
|
||||||
|
ImageSourceNorth {
|
||||||
|
scale: Vec2<f32>,
|
||||||
|
},
|
||||||
|
/// Draw an image from the texture at `tex` in the fragment shader, with the
|
||||||
|
/// target rectangle rotated to face north. (TODO: detail on what "target"
|
||||||
|
/// means)
|
||||||
|
ImageTargetNorth {
|
||||||
|
scale: Vec2<f32>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode {
|
impl Mode {
|
||||||
fn value(self) -> u32 {
|
fn value(self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
Mode::Text => MODE_TEXT,
|
Mode::Text => MODE_TEXT,
|
||||||
Mode::Image => MODE_IMAGE,
|
Mode::Image { .. } => MODE_IMAGE,
|
||||||
Mode::Geometry => MODE_GEOMETRY,
|
Mode::Geometry => MODE_GEOMETRY,
|
||||||
Mode::ImageSourceNorth => MODE_IMAGE_SOURCE_NORTH,
|
Mode::ImageSourceNorth { .. } => MODE_IMAGE_SOURCE_NORTH,
|
||||||
Mode::ImageTargetNorth => MODE_IMAGE_TARGET_NORTH,
|
Mode::ImageTargetNorth { .. } => MODE_IMAGE_TARGET_NORTH,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the scaling of the displayed image compared to the source.
|
||||||
|
fn scale(self) -> Vec2<f32> {
|
||||||
|
match self {
|
||||||
|
Mode::ImageSourceNorth { scale } | Mode::ImageTargetNorth { scale } => scale,
|
||||||
|
Mode::Image { scale } => scale,
|
||||||
|
Mode::Text | Mode::Geometry => Vec2::one(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,8 +143,8 @@ pub struct TextureBindGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct UiLayout {
|
pub struct UiLayout {
|
||||||
pub locals: wgpu::BindGroupLayout,
|
locals: wgpu::BindGroupLayout,
|
||||||
pub texture: wgpu::BindGroupLayout,
|
texture: wgpu::BindGroupLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiLayout {
|
impl UiLayout {
|
||||||
@ -137,6 +189,17 @@ impl UiLayout {
|
|||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
|
// tex_locals
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -158,7 +221,12 @@ impl UiLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bind_texture(&self, device: &wgpu::Device, texture: &Texture) -> TextureBindGroup {
|
pub fn bind_texture(
|
||||||
|
&self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
texture: &Texture,
|
||||||
|
tex_locals: Consts<TexLocals>,
|
||||||
|
) -> TextureBindGroup {
|
||||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
layout: &self.texture,
|
layout: &self.texture,
|
||||||
@ -171,6 +239,10 @@ impl UiLayout {
|
|||||||
binding: 1,
|
binding: 1,
|
||||||
resource: wgpu::BindingResource::Sampler(&texture.sampler),
|
resource: wgpu::BindingResource::Sampler(&texture.sampler),
|
||||||
},
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: tex_locals.buf().as_entire_binding(),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -268,17 +340,19 @@ pub fn create_quad_vert_gradient(
|
|||||||
let top_color = top_color.into_array();
|
let top_color = top_color.into_array();
|
||||||
let bottom_color = bottom_color.into_array();
|
let bottom_color = bottom_color.into_array();
|
||||||
|
|
||||||
let center = if let Mode::ImageSourceNorth = mode {
|
let center = if let Mode::ImageSourceNorth { .. } = mode {
|
||||||
uv_rect.center().into_array()
|
uv_rect.center().into_array()
|
||||||
} else {
|
} else {
|
||||||
rect.center().into_array()
|
rect.center().into_array()
|
||||||
};
|
};
|
||||||
|
let scale = mode.scale().into_array();
|
||||||
let mode_val = mode.value();
|
let mode_val = mode.value();
|
||||||
let v = |pos, uv, color| Vertex {
|
let v = |pos, uv, color| Vertex {
|
||||||
pos,
|
pos,
|
||||||
uv,
|
uv,
|
||||||
center,
|
center,
|
||||||
color,
|
color,
|
||||||
|
scale,
|
||||||
mode: mode_val,
|
mode: mode_val,
|
||||||
};
|
};
|
||||||
let aabr_to_lbrt = |aabr: Aabr<f32>| (aabr.min.x, aabr.min.y, aabr.max.x, aabr.max.y);
|
let aabr_to_lbrt = |aabr: Aabr<f32>| (aabr.min.x, aabr.min.y, aabr.max.x, aabr.max.y);
|
||||||
@ -315,12 +389,14 @@ pub fn create_tri(
|
|||||||
mode: Mode,
|
mode: Mode,
|
||||||
) -> Tri<Vertex> {
|
) -> Tri<Vertex> {
|
||||||
let center = [0.0, 0.0];
|
let center = [0.0, 0.0];
|
||||||
|
let scale = mode.scale().into_array();
|
||||||
let mode_val = mode.value();
|
let mode_val = mode.value();
|
||||||
let v = |pos, uv| Vertex {
|
let v = |pos, uv| Vertex {
|
||||||
pos,
|
pos,
|
||||||
uv,
|
uv,
|
||||||
center,
|
center,
|
||||||
color: color.into_array(),
|
color: color.into_array(),
|
||||||
|
scale,
|
||||||
mode: mode_val,
|
mode: mode_val,
|
||||||
};
|
};
|
||||||
Tri::new(
|
Tri::new(
|
||||||
@ -329,3 +405,298 @@ pub fn create_tri(
|
|||||||
v([tri[2][0], tri[2][1]], [uv_tri[2][0], uv_tri[2][1]]),
|
v([tri[2][0], tri[2][1]], [uv_tri[2][0], uv_tri[2][1]]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Premultiplying alpha on the GPU before placing images into the textures that
|
||||||
|
// will be sampled from in the UI pipeline.
|
||||||
|
//
|
||||||
|
// Steps:
|
||||||
|
//
|
||||||
|
// 1. Upload new image via `Device::create_texture_with_data`.
|
||||||
|
//
|
||||||
|
// (NOTE: Initially considered: Creating a storage buffer to read from in the
|
||||||
|
// shader via `Device::create_buffer_init`, with `MAP_WRITE` flag to avoid
|
||||||
|
// staging buffer. However, with GPUs combining usages other than `COPY_SRC`
|
||||||
|
// with `MAP_WRITE` may be less ideal. Plus, by copying into a texture first
|
||||||
|
// we can get free srgb conversion when fetching colors from the texture. In
|
||||||
|
// the future, we may want to branch based on the whether the GPU is
|
||||||
|
// integrated and avoid this extra copy.)
|
||||||
|
//
|
||||||
|
// 2. Run render pipeline to multiply by alpha reading from this texture and
|
||||||
|
// writing to the final texture (this can either be in an atlas or in an
|
||||||
|
// independent texture if the image is over a certain size threshold).
|
||||||
|
//
|
||||||
|
// (NOTE: Initially considered: using a compute pipeline and writing to the
|
||||||
|
// final texture as a storage texture. However, the srgb format can't be
|
||||||
|
// used with storage texture and there is not yet the capability to create
|
||||||
|
// non-srgb views of srgb textures.)
|
||||||
|
//
|
||||||
|
// Info needed:
|
||||||
|
//
|
||||||
|
// * source texture (texture binding)
|
||||||
|
// * target texture (render attachment)
|
||||||
|
// * source image dimensions (push constant)
|
||||||
|
// * target texture dimensions (push constant)
|
||||||
|
// * position in the target texture (push constant)
|
||||||
|
//
|
||||||
|
// TODO: potential optimizations
|
||||||
|
// * what is the overhead of this draw call call? at some point we may be better
|
||||||
|
// off converting very small images on the cpu and/or batching these into a
|
||||||
|
// single draw call
|
||||||
|
// * what is the overhead of creating new small textures? for processing many
|
||||||
|
// small images would it be useful to create a single texture the same size as
|
||||||
|
// our cache texture and use Queue::write_texture?
|
||||||
|
// * is using create_buffer_init and reading directly from that (with manual
|
||||||
|
// srgb conversion) worth avoiding staging buffer/copy-to-texture for
|
||||||
|
// integrated GPUs?
|
||||||
|
// * premultipying alpha in a release asset preparation step
|
||||||
|
|
||||||
|
pub struct PremultiplyAlphaLayout {
|
||||||
|
source_texture: wgpu::BindGroupLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PremultiplyAlphaLayout {
|
||||||
|
pub fn new(device: &wgpu::Device) -> Self {
|
||||||
|
Self {
|
||||||
|
source_texture: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
entries: &[
|
||||||
|
// source_texture
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PremultiplyAlphaPipeline {
|
||||||
|
pub pipeline: wgpu::RenderPipeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PremultiplyAlphaPipeline {
|
||||||
|
pub fn new(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
vs_module: &wgpu::ShaderModule,
|
||||||
|
fs_module: &wgpu::ShaderModule,
|
||||||
|
layout: &PremultiplyAlphaLayout,
|
||||||
|
) -> Self {
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("Premultiply alpha pipeline layout"),
|
||||||
|
bind_group_layouts: &[&layout.source_texture],
|
||||||
|
push_constant_ranges: &[wgpu::PushConstantRange {
|
||||||
|
stages: wgpu::ShaderStage::VERTEX,
|
||||||
|
range: 0..core::mem::size_of::<PremultiplyAlphaParams>() as u32,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("Premultiply alpha pipeline"),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: vs_module,
|
||||||
|
entry_point: "main",
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
|
strip_index_format: None,
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
|
clamp_depth: false,
|
||||||
|
polygon_mode: wgpu::PolygonMode::Fill,
|
||||||
|
conservative: false,
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: fs_module,
|
||||||
|
entry_point: "main",
|
||||||
|
targets: &[wgpu::ColorTargetState {
|
||||||
|
format: UI_IMAGE_FORMAT,
|
||||||
|
blend: None,
|
||||||
|
write_mask: wgpu::ColorWrite::ALL,
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
Self { pipeline }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uploaded as push constant.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
||||||
|
pub struct PremultiplyAlphaParams {
|
||||||
|
/// Size of the source image.
|
||||||
|
source_size_xy: u32,
|
||||||
|
/// Offset to place the image at in the target texture.
|
||||||
|
///
|
||||||
|
/// Origin is the top-left.
|
||||||
|
target_offset_xy: u32,
|
||||||
|
/// Size of the target texture.
|
||||||
|
target_size_xy: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An image upload that needs alpha premultiplication and which is in a pending
|
||||||
|
/// state.
|
||||||
|
///
|
||||||
|
/// From here we will use the `PremultiplyAlpha` pipeline to premultiply the
|
||||||
|
/// alpha while transfering the image to its destination texture.
|
||||||
|
pub(in super::super) struct PremultiplyUpload {
|
||||||
|
source_bg: wgpu::BindGroup,
|
||||||
|
source_size_xy: u32,
|
||||||
|
/// The location in the final texture this will be placed at. Technically,
|
||||||
|
/// we don't need this information at this point but it is convenient to
|
||||||
|
/// store it here.
|
||||||
|
offset: Vec2<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PremultiplyUpload {
|
||||||
|
pub(in super::super) fn prepare(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
layout: &PremultiplyAlphaLayout,
|
||||||
|
image: &image::RgbaImage,
|
||||||
|
offset: Vec2<u16>,
|
||||||
|
) -> Self {
|
||||||
|
// TODO: duplicating some code from `Texture` since:
|
||||||
|
// 1. We don't need to create a sampler.
|
||||||
|
// 2. Texture::new accepts &DynamicImage which isn't possible to create from
|
||||||
|
// &RgbaImage without cloning. (this might be addressed on zoomy worldgen
|
||||||
|
// branch)
|
||||||
|
let image_size = wgpu::Extent3d {
|
||||||
|
width: image.width(),
|
||||||
|
height: image.height(),
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
};
|
||||||
|
let source_tex = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: None,
|
||||||
|
size: image_size,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
|
||||||
|
});
|
||||||
|
queue.write_texture(
|
||||||
|
wgpu::ImageCopyTexture {
|
||||||
|
texture: &source_tex,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
},
|
||||||
|
&(&**image)[..(image.width() as usize * image.height() as usize * 4)],
|
||||||
|
wgpu::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: NonZeroU32::new(image.width() * 4),
|
||||||
|
rows_per_image: NonZeroU32::new(image.height()),
|
||||||
|
},
|
||||||
|
image_size,
|
||||||
|
);
|
||||||
|
// Create view to use to create bind group
|
||||||
|
let view = source_tex.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
label: None,
|
||||||
|
format: Some(wgpu::TextureFormat::Rgba8UnormSrgb),
|
||||||
|
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
base_mip_level: 0,
|
||||||
|
mip_level_count: None,
|
||||||
|
base_array_layer: 0,
|
||||||
|
array_layer_count: None,
|
||||||
|
});
|
||||||
|
let source_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: &layout.source_texture,
|
||||||
|
entries: &[wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&view),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
// NOTE: We assume the max texture size is less than u16::MAX.
|
||||||
|
let source_size_xy = image_size.width + (image_size.height << 16);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
source_bg,
|
||||||
|
source_size_xy,
|
||||||
|
offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Semantically, this consumes the `PremultiplyUpload` but we need to keep
|
||||||
|
/// the bind group alive to the end of the render pass and don't want to
|
||||||
|
/// bother storing it somewhere else.
|
||||||
|
pub(in super::super) fn draw_data(
|
||||||
|
&self,
|
||||||
|
target: &Texture,
|
||||||
|
) -> (&wgpu::BindGroup, PremultiplyAlphaParams) {
|
||||||
|
let target_offset_xy = u32::from(self.offset.x) + (u32::from(self.offset.y) << 16);
|
||||||
|
let target_dims = target.get_dimensions();
|
||||||
|
// NOTE: We assume the max texture size is less than u16::MAX.
|
||||||
|
let target_size_xy = target_dims.x + (target_dims.y << 16);
|
||||||
|
(&self.source_bg, PremultiplyAlphaParams {
|
||||||
|
source_size_xy: self.source_size_xy,
|
||||||
|
target_offset_xy,
|
||||||
|
target_size_xy,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
/// Per-target texture batched uploads
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(in super::super) struct BatchedUploads {
|
||||||
|
batches: Vec<(Arc<Texture>, Vec<PremultiplyUpload>)>,
|
||||||
|
}
|
||||||
|
#[derive(Default, Clone, Copy)]
|
||||||
|
pub struct UploadBatchId(usize);
|
||||||
|
|
||||||
|
impl BatchedUploads {
|
||||||
|
/// Adds the provided upload to the batch indicated by the provided target
|
||||||
|
/// texture and optional batch id. A new batch will be created if the batch
|
||||||
|
/// id is invalid (doesn't refer to an existing batch) or the provided
|
||||||
|
/// target texture isn't the same as the one associated with the
|
||||||
|
/// provided batch id. Creating a new batch involves cloning the
|
||||||
|
/// provided texture `Arc`.
|
||||||
|
///
|
||||||
|
/// The id of the batch where the upload is ultimately submitted will be
|
||||||
|
/// returned. This id can be used in subsequent calls to add items to
|
||||||
|
/// the same batch (i.e. uploads for the same texture).
|
||||||
|
///
|
||||||
|
/// Batch ids will reset every frame, however since we check that the
|
||||||
|
/// texture matches, it is perfectly fine to use a stale id (just keep
|
||||||
|
/// in mind that this will create a new batch). This also means that it is
|
||||||
|
/// sufficient to use `UploadBatchId::default()` when calling this with
|
||||||
|
/// new textures.
|
||||||
|
pub(in super::super) fn submit(
|
||||||
|
&mut self,
|
||||||
|
target_texture: &Arc<Texture>,
|
||||||
|
batch_id: UploadBatchId,
|
||||||
|
upload: PremultiplyUpload,
|
||||||
|
) -> UploadBatchId {
|
||||||
|
if let Some(batch) = self
|
||||||
|
.batches
|
||||||
|
.get_mut(batch_id.0)
|
||||||
|
.filter(|b| Arc::ptr_eq(&b.0, target_texture))
|
||||||
|
{
|
||||||
|
batch.1.push(upload);
|
||||||
|
batch_id
|
||||||
|
} else {
|
||||||
|
let new_batch_id = UploadBatchId(self.batches.len());
|
||||||
|
self.batches
|
||||||
|
.push((Arc::clone(target_texture), vec![upload]));
|
||||||
|
new_batch_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in super::super) fn take(&mut self) -> Vec<(Arc<Texture>, Vec<PremultiplyUpload>)> {
|
||||||
|
core::mem::take(&mut self.batches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -63,6 +63,7 @@ struct ImmutableLayouts {
|
|||||||
clouds: clouds::CloudsLayout,
|
clouds: clouds::CloudsLayout,
|
||||||
bloom: bloom::BloomLayout,
|
bloom: bloom::BloomLayout,
|
||||||
ui: ui::UiLayout,
|
ui: ui::UiLayout,
|
||||||
|
premultiply_alpha: ui::PremultiplyAlphaLayout,
|
||||||
blit: blit::BlitLayout,
|
blit: blit::BlitLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,6 +178,8 @@ pub struct Renderer {
|
|||||||
profile_times: Vec<wgpu_profiler::GpuTimerScopeResult>,
|
profile_times: Vec<wgpu_profiler::GpuTimerScopeResult>,
|
||||||
profiler_features_enabled: bool,
|
profiler_features_enabled: bool,
|
||||||
|
|
||||||
|
ui_premultiply_uploads: ui::BatchedUploads,
|
||||||
|
|
||||||
#[cfg(feature = "egui-ui")]
|
#[cfg(feature = "egui-ui")]
|
||||||
egui_renderpass: egui_wgpu_backend::RenderPass,
|
egui_renderpass: egui_wgpu_backend::RenderPass,
|
||||||
|
|
||||||
@ -393,6 +396,7 @@ impl Renderer {
|
|||||||
&pipeline_modes,
|
&pipeline_modes,
|
||||||
));
|
));
|
||||||
let ui = ui::UiLayout::new(&device);
|
let ui = ui::UiLayout::new(&device);
|
||||||
|
let premultiply_alpha = ui::PremultiplyAlphaLayout::new(&device);
|
||||||
let blit = blit::BlitLayout::new(&device);
|
let blit = blit::BlitLayout::new(&device);
|
||||||
|
|
||||||
let immutable = Arc::new(ImmutableLayouts {
|
let immutable = Arc::new(ImmutableLayouts {
|
||||||
@ -407,6 +411,7 @@ impl Renderer {
|
|||||||
clouds,
|
clouds,
|
||||||
bloom,
|
bloom,
|
||||||
ui,
|
ui,
|
||||||
|
premultiply_alpha,
|
||||||
blit,
|
blit,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -542,6 +547,8 @@ impl Renderer {
|
|||||||
profile_times: Vec::new(),
|
profile_times: Vec::new(),
|
||||||
profiler_features_enabled,
|
profiler_features_enabled,
|
||||||
|
|
||||||
|
ui_premultiply_uploads: Default::default(),
|
||||||
|
|
||||||
#[cfg(feature = "egui-ui")]
|
#[cfg(feature = "egui-ui")]
|
||||||
egui_renderpass,
|
egui_renderpass,
|
||||||
|
|
||||||
@ -1434,6 +1441,25 @@ impl Renderer {
|
|||||||
texture.update(&self.queue, offset, size, bytemuck::cast_slice(data))
|
texture.update(&self.queue, offset, size, bytemuck::cast_slice(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See docs on [`ui::BatchedUploads::submit`].
|
||||||
|
pub fn ui_premultiply_upload(
|
||||||
|
&mut self,
|
||||||
|
target_texture: &Arc<Texture>,
|
||||||
|
batch: ui::UploadBatchId,
|
||||||
|
image: &image::RgbaImage,
|
||||||
|
offset: Vec2<u16>,
|
||||||
|
) -> ui::UploadBatchId {
|
||||||
|
let upload = ui::PremultiplyUpload::prepare(
|
||||||
|
&self.device,
|
||||||
|
&self.queue,
|
||||||
|
&self.layouts.premultiply_alpha,
|
||||||
|
image,
|
||||||
|
offset,
|
||||||
|
);
|
||||||
|
self.ui_premultiply_uploads
|
||||||
|
.submit(target_texture, batch, upload)
|
||||||
|
}
|
||||||
|
|
||||||
/// Queue to obtain a screenshot on the next frame render
|
/// Queue to obtain a screenshot on the next frame render
|
||||||
pub fn create_screenshot(
|
pub fn create_screenshot(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -47,8 +47,12 @@ impl Renderer {
|
|||||||
self.layouts.ui.bind_locals(&self.device, locals)
|
self.layouts.ui.bind_locals(&self.device, locals)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ui_bind_texture(&self, texture: &Texture) -> ui::TextureBindGroup {
|
pub fn ui_bind_texture(&mut self, texture: &Texture) -> ui::TextureBindGroup {
|
||||||
self.layouts.ui.bind_texture(&self.device, texture)
|
let tex_locals = ui::TexLocals::from(texture.get_dimensions().xy());
|
||||||
|
let tex_locals_consts = self.create_consts(&[tex_locals]);
|
||||||
|
self.layouts
|
||||||
|
.ui
|
||||||
|
.bind_texture(&self.device, texture, tex_locals_consts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_figure_bound_locals(
|
pub fn create_figure_bound_locals(
|
||||||
|
@ -12,6 +12,7 @@ use super::{
|
|||||||
rain_occlusion_map::{RainOcclusionMap, RainOcclusionMapRenderer},
|
rain_occlusion_map::{RainOcclusionMap, RainOcclusionMapRenderer},
|
||||||
Renderer, ShadowMap, ShadowMapRenderer,
|
Renderer, ShadowMap, ShadowMapRenderer,
|
||||||
};
|
};
|
||||||
|
use common_base::prof_span;
|
||||||
use core::{num::NonZeroU32, ops::Range};
|
use core::{num::NonZeroU32, ops::Range};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vek::Aabr;
|
use vek::Aabr;
|
||||||
@ -19,6 +20,9 @@ use wgpu_profiler::scope::{ManualOwningScope, OwningScope, Scope};
|
|||||||
#[cfg(feature = "egui-ui")]
|
#[cfg(feature = "egui-ui")]
|
||||||
use {common_base::span, egui_wgpu_backend::ScreenDescriptor, egui_winit_platform::Platform};
|
use {common_base::span, egui_wgpu_backend::ScreenDescriptor, egui_winit_platform::Platform};
|
||||||
|
|
||||||
|
/// Gpu timing label prefix associated with the UI alpha premultiplication pass.
|
||||||
|
pub const UI_PREMULTIPLY_PASS: &str = "ui_premultiply_pass";
|
||||||
|
|
||||||
// Currently available pipelines
|
// Currently available pipelines
|
||||||
enum Pipelines<'frame> {
|
enum Pipelines<'frame> {
|
||||||
Interface(&'frame super::InterfacePipelines),
|
Interface(&'frame super::InterfacePipelines),
|
||||||
@ -36,6 +40,14 @@ impl<'frame> Pipelines<'frame> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn premultiply_alpha(&self) -> Option<&ui::PremultiplyAlphaPipeline> {
|
||||||
|
match self {
|
||||||
|
Pipelines::Interface(pipelines) => Some(&pipelines.premultiply_alpha),
|
||||||
|
Pipelines::All(pipelines) => Some(&pipelines.premultiply_alpha),
|
||||||
|
Pipelines::None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn blit(&self) -> Option<&blit::BlitPipeline> {
|
fn blit(&self) -> Option<&blit::BlitPipeline> {
|
||||||
match self {
|
match self {
|
||||||
Pipelines::Interface(pipelines) => Some(&pipelines.blit),
|
Pipelines::Interface(pipelines) => Some(&pipelines.blit),
|
||||||
@ -66,6 +78,7 @@ struct RendererBorrow<'frame> {
|
|||||||
pipeline_modes: &'frame super::PipelineModes,
|
pipeline_modes: &'frame super::PipelineModes,
|
||||||
quad_index_buffer_u16: &'frame Buffer<u16>,
|
quad_index_buffer_u16: &'frame Buffer<u16>,
|
||||||
quad_index_buffer_u32: &'frame Buffer<u32>,
|
quad_index_buffer_u32: &'frame Buffer<u32>,
|
||||||
|
ui_premultiply_uploads: &'frame mut ui::BatchedUploads,
|
||||||
#[cfg(feature = "egui-ui")]
|
#[cfg(feature = "egui-ui")]
|
||||||
egui_render_pass: &'frame mut egui_wgpu_backend::RenderPass,
|
egui_render_pass: &'frame mut egui_wgpu_backend::RenderPass,
|
||||||
}
|
}
|
||||||
@ -117,6 +130,7 @@ impl<'frame> Drawer<'frame> {
|
|||||||
pipeline_modes: &renderer.pipeline_modes,
|
pipeline_modes: &renderer.pipeline_modes,
|
||||||
quad_index_buffer_u16: &renderer.quad_index_buffer_u16,
|
quad_index_buffer_u16: &renderer.quad_index_buffer_u16,
|
||||||
quad_index_buffer_u32: &renderer.quad_index_buffer_u32,
|
quad_index_buffer_u32: &renderer.quad_index_buffer_u32,
|
||||||
|
ui_premultiply_uploads: &mut renderer.ui_premultiply_uploads,
|
||||||
#[cfg(feature = "egui-ui")]
|
#[cfg(feature = "egui-ui")]
|
||||||
egui_render_pass: &mut renderer.egui_renderpass,
|
egui_render_pass: &mut renderer.egui_renderpass,
|
||||||
};
|
};
|
||||||
@ -424,7 +438,49 @@ impl<'frame> Drawer<'frame> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runs render passes with alpha premultiplication pipeline to complete any
|
||||||
|
/// pending uploads.
|
||||||
|
fn run_ui_premultiply_passes(&mut self) {
|
||||||
|
prof_span!("run_ui_premultiply_passes");
|
||||||
|
let Some(premultiply_alpha) = self.borrow.pipelines.premultiply_alpha() else { return };
|
||||||
|
let encoder = self.encoder.as_mut().unwrap();
|
||||||
|
let device = self.borrow.device;
|
||||||
|
|
||||||
|
let targets = self.borrow.ui_premultiply_uploads.take();
|
||||||
|
|
||||||
|
for (i, (target_texture, uploads)) in targets.into_iter().enumerate() {
|
||||||
|
prof_span!("ui premultiply pass");
|
||||||
|
let profile_name = format!("{UI_PREMULTIPLY_PASS} {i}");
|
||||||
|
let label = format!("ui premultiply pass {i}");
|
||||||
|
let mut render_pass =
|
||||||
|
encoder.scoped_render_pass(&profile_name, device, &wgpu::RenderPassDescriptor {
|
||||||
|
label: Some(&label),
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &target_texture.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Load,
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
render_pass.set_pipeline(&premultiply_alpha.pipeline);
|
||||||
|
for upload in &uploads {
|
||||||
|
let (source_bind_group, push_constant_data) = upload.draw_data(&target_texture);
|
||||||
|
let bytes = bytemuck::bytes_of(&push_constant_data);
|
||||||
|
render_pass.set_bind_group(0, source_bind_group, &[]);
|
||||||
|
render_pass.set_push_constants(wgpu::ShaderStage::VERTEX, 0, bytes);
|
||||||
|
render_pass.draw(0..6, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Note, this automatically calls the internal `run_ui_premultiply_passes`
|
||||||
|
/// to complete any pending image uploads for the UI.
|
||||||
pub fn third_pass(&mut self) -> ThirdPassDrawer {
|
pub fn third_pass(&mut self) -> ThirdPassDrawer {
|
||||||
|
self.run_ui_premultiply_passes();
|
||||||
|
|
||||||
let encoder = self.encoder.as_mut().unwrap();
|
let encoder = self.encoder.as_mut().unwrap();
|
||||||
let device = self.borrow.device;
|
let device = self.borrow.device;
|
||||||
let mut render_pass =
|
let mut render_pass =
|
||||||
@ -498,7 +554,7 @@ impl<'frame> Drawer<'frame> {
|
|||||||
|
|
||||||
/// Does nothing if the shadow pipelines are not available or shadow map
|
/// Does nothing if the shadow pipelines are not available or shadow map
|
||||||
/// rendering is disabled
|
/// rendering is disabled
|
||||||
pub fn draw_point_shadows<'data: 'frame>(
|
pub fn draw_point_shadows<'data>(
|
||||||
&mut self,
|
&mut self,
|
||||||
matrices: &[shadow::PointLightMatrix; 126],
|
matrices: &[shadow::PointLightMatrix; 126],
|
||||||
chunks: impl Clone
|
chunks: impl Clone
|
||||||
|
@ -33,6 +33,7 @@ pub struct Pipelines {
|
|||||||
pub lod_object: lod_object::LodObjectPipeline,
|
pub lod_object: lod_object::LodObjectPipeline,
|
||||||
pub terrain: terrain::TerrainPipeline,
|
pub terrain: terrain::TerrainPipeline,
|
||||||
pub ui: ui::UiPipeline,
|
pub ui: ui::UiPipeline,
|
||||||
|
pub premultiply_alpha: ui::PremultiplyAlphaPipeline,
|
||||||
pub blit: blit::BlitPipeline,
|
pub blit: blit::BlitPipeline,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +80,7 @@ pub struct IngameAndShadowPipelines {
|
|||||||
/// Use to decouple interface pipeline creation when initializing the renderer
|
/// Use to decouple interface pipeline creation when initializing the renderer
|
||||||
pub struct InterfacePipelines {
|
pub struct InterfacePipelines {
|
||||||
pub ui: ui::UiPipeline,
|
pub ui: ui::UiPipeline,
|
||||||
|
pub premultiply_alpha: ui::PremultiplyAlphaPipeline,
|
||||||
pub blit: blit::BlitPipeline,
|
pub blit: blit::BlitPipeline,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +102,7 @@ impl Pipelines {
|
|||||||
lod_object: ingame.lod_object,
|
lod_object: ingame.lod_object,
|
||||||
terrain: ingame.terrain,
|
terrain: ingame.terrain,
|
||||||
ui: interface.ui,
|
ui: interface.ui,
|
||||||
|
premultiply_alpha: interface.premultiply_alpha,
|
||||||
blit: interface.blit,
|
blit: interface.blit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,6 +130,8 @@ struct ShaderModules {
|
|||||||
trail_frag: wgpu::ShaderModule,
|
trail_frag: wgpu::ShaderModule,
|
||||||
ui_vert: wgpu::ShaderModule,
|
ui_vert: wgpu::ShaderModule,
|
||||||
ui_frag: wgpu::ShaderModule,
|
ui_frag: wgpu::ShaderModule,
|
||||||
|
premultiply_alpha_vert: wgpu::ShaderModule,
|
||||||
|
premultiply_alpha_frag: wgpu::ShaderModule,
|
||||||
lod_terrain_vert: wgpu::ShaderModule,
|
lod_terrain_vert: wgpu::ShaderModule,
|
||||||
lod_terrain_frag: wgpu::ShaderModule,
|
lod_terrain_frag: wgpu::ShaderModule,
|
||||||
clouds_vert: wgpu::ShaderModule,
|
clouds_vert: wgpu::ShaderModule,
|
||||||
@ -336,6 +341,8 @@ impl ShaderModules {
|
|||||||
trail_frag: create_shader("trail-frag", ShaderKind::Fragment)?,
|
trail_frag: create_shader("trail-frag", ShaderKind::Fragment)?,
|
||||||
ui_vert: create_shader("ui-vert", ShaderKind::Vertex)?,
|
ui_vert: create_shader("ui-vert", ShaderKind::Vertex)?,
|
||||||
ui_frag: create_shader("ui-frag", ShaderKind::Fragment)?,
|
ui_frag: create_shader("ui-frag", ShaderKind::Fragment)?,
|
||||||
|
premultiply_alpha_vert: create_shader("premultiply-alpha-vert", ShaderKind::Vertex)?,
|
||||||
|
premultiply_alpha_frag: create_shader("premultiply-alpha-frag", ShaderKind::Fragment)?,
|
||||||
lod_terrain_vert: create_shader("lod-terrain-vert", ShaderKind::Vertex)?,
|
lod_terrain_vert: create_shader("lod-terrain-vert", ShaderKind::Vertex)?,
|
||||||
lod_terrain_frag: create_shader("lod-terrain-frag", ShaderKind::Fragment)?,
|
lod_terrain_frag: create_shader("lod-terrain-frag", ShaderKind::Fragment)?,
|
||||||
clouds_vert: create_shader("clouds-vert", ShaderKind::Vertex)?,
|
clouds_vert: create_shader("clouds-vert", ShaderKind::Vertex)?,
|
||||||
@ -416,11 +423,11 @@ struct PipelineNeeds<'a> {
|
|||||||
fn create_interface_pipelines(
|
fn create_interface_pipelines(
|
||||||
needs: PipelineNeeds,
|
needs: PipelineNeeds,
|
||||||
pool: &rayon::ThreadPool,
|
pool: &rayon::ThreadPool,
|
||||||
tasks: [Task; 2],
|
tasks: [Task; 3],
|
||||||
) -> InterfacePipelines {
|
) -> InterfacePipelines {
|
||||||
prof_span!(_guard, "create_interface_pipelines");
|
prof_span!(_guard, "create_interface_pipelines");
|
||||||
|
|
||||||
let [ui_task, blit_task] = tasks;
|
let [ui_task, premultiply_alpha_task, blit_task] = tasks;
|
||||||
// Construct a pipeline for rendering UI elements
|
// Construct a pipeline for rendering UI elements
|
||||||
let create_ui = || {
|
let create_ui = || {
|
||||||
ui_task.run(
|
ui_task.run(
|
||||||
@ -438,6 +445,20 @@ fn create_interface_pipelines(
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let create_premultiply_alpha = || {
|
||||||
|
premultiply_alpha_task.run(
|
||||||
|
|| {
|
||||||
|
ui::PremultiplyAlphaPipeline::new(
|
||||||
|
needs.device,
|
||||||
|
&needs.shaders.premultiply_alpha_vert,
|
||||||
|
&needs.shaders.premultiply_alpha_frag,
|
||||||
|
&needs.layouts.premultiply_alpha,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"premultiply alpha pipeline creation",
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
// Construct a pipeline for blitting, used during screenshotting
|
// Construct a pipeline for blitting, used during screenshotting
|
||||||
let create_blit = || {
|
let create_blit = || {
|
||||||
blit_task.run(
|
blit_task.run(
|
||||||
@ -454,9 +475,15 @@ fn create_interface_pipelines(
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let (ui, blit) = pool.join(create_ui, create_blit);
|
let (ui, (premultiply_alpha, blit)) = pool.join(create_ui, || {
|
||||||
|
pool.join(create_premultiply_alpha, create_blit)
|
||||||
|
});
|
||||||
|
|
||||||
InterfacePipelines { ui, blit }
|
InterfacePipelines {
|
||||||
|
ui,
|
||||||
|
premultiply_alpha,
|
||||||
|
blit,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create IngamePipelines and shadow pipelines in parallel
|
/// Create IngamePipelines and shadow pipelines in parallel
|
||||||
|
@ -73,6 +73,8 @@ impl assets::Compound for Shaders {
|
|||||||
"trail-frag",
|
"trail-frag",
|
||||||
"ui-vert",
|
"ui-vert",
|
||||||
"ui-frag",
|
"ui-frag",
|
||||||
|
"premultiply-alpha-vert",
|
||||||
|
"premultiply-alpha-frag",
|
||||||
"lod-terrain-vert",
|
"lod-terrain-vert",
|
||||||
"lod-terrain-frag",
|
"lod-terrain-frag",
|
||||||
"clouds-vert",
|
"clouds-vert",
|
||||||
|
@ -136,8 +136,8 @@ impl Texture {
|
|||||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
mag_filter: wgpu::FilterMode::Nearest,
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
min_filter: wgpu::FilterMode::Nearest,
|
min_filter: wgpu::FilterMode::Linear,
|
||||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
@ -224,6 +224,7 @@ impl Texture {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove `get` from this name
|
||||||
/// Get dimensions of the represented image.
|
/// Get dimensions of the represented image.
|
||||||
pub fn get_dimensions(&self) -> vek::Vec3<u32> {
|
pub fn get_dimensions(&self) -> vek::Vec3<u32> {
|
||||||
vek::Vec3::new(
|
vek::Vec3::new(
|
||||||
|
@ -1232,9 +1232,9 @@ impl Scene {
|
|||||||
pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group }
|
pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group }
|
||||||
|
|
||||||
/// Render the scene using the provided `Drawer`.
|
/// Render the scene using the provided `Drawer`.
|
||||||
pub fn render<'a>(
|
pub fn render(
|
||||||
&'a self,
|
&self,
|
||||||
drawer: &mut Drawer<'a>,
|
drawer: &mut Drawer<'_>,
|
||||||
state: &State,
|
state: &State,
|
||||||
viewpoint_entity: EcsEntity,
|
viewpoint_entity: EcsEntity,
|
||||||
tick: u64,
|
tick: u64,
|
||||||
|
@ -1920,7 +1920,7 @@ impl PlayState for SessionState {
|
|||||||
/// Render the session to the screen.
|
/// Render the session to the screen.
|
||||||
///
|
///
|
||||||
/// This method should be called once per frame.
|
/// This method should be called once per frame.
|
||||||
fn render<'a>(&'a self, drawer: &mut Drawer<'a>, settings: &Settings) {
|
fn render(&self, drawer: &mut Drawer<'_>, settings: &Settings) {
|
||||||
span!(_guard, "render", "<Session as PlayState>::render");
|
span!(_guard, "render", "<Session as PlayState>::render");
|
||||||
|
|
||||||
let client = self.client.borrow();
|
let client = self.client.borrow();
|
||||||
|
@ -7,6 +7,9 @@ use conrod_core::{text::GlyphCache, widget::Id};
|
|||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
// TODO: probably make cache fields where we have mut getters into just public
|
||||||
|
// fields
|
||||||
|
|
||||||
// Multiplied by current window size
|
// Multiplied by current window size
|
||||||
const GLYPH_CACHE_SIZE: u32 = 1;
|
const GLYPH_CACHE_SIZE: u32 = 1;
|
||||||
// Glyph cache tolerances
|
// Glyph cache tolerances
|
||||||
@ -51,7 +54,9 @@ impl Cache {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn glyph_cache_tex(&self) -> &(Texture, UiTextureBindGroup) { &self.glyph_cache_tex }
|
pub fn glyph_cache_tex(&self) -> (&Texture, &UiTextureBindGroup) {
|
||||||
|
(&self.glyph_cache_tex.0, &self.glyph_cache_tex.1)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cache_mut_and_tex(
|
pub fn cache_mut_and_tex(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -11,10 +11,13 @@ const EPSILON: f32 = 0.0001;
|
|||||||
|
|
||||||
// Averaging colors with alpha such that when blending with the background color
|
// Averaging colors with alpha such that when blending with the background color
|
||||||
// the same color will be produced as when the individual colors were blended
|
// the same color will be produced as when the individual colors were blended
|
||||||
// with the background and then averaged
|
// with the background and then averaged.
|
||||||
|
//
|
||||||
// Say we have two areas that we are combining to form a single pixel
|
// Say we have two areas that we are combining to form a single pixel
|
||||||
// A1 and A2 where these are the fraction of the area of the pixel each color
|
// A1 and A2 where these are the fraction of the area of the pixel each color
|
||||||
// contributes to Then if the colors were opaque we would say that the final
|
// contributes to.
|
||||||
|
//
|
||||||
|
// Then if the colors were opaque we would say that the final
|
||||||
// color output color o3 is
|
// color output color o3 is
|
||||||
// E1: o3 = A1 * o1 + A2 * o2
|
// E1: o3 = A1 * o1 + A2 * o2
|
||||||
// where o1 and o2 are the opaque colors of the two areas
|
// where o1 and o2 are the opaque colors of the two areas
|
||||||
@ -30,7 +33,7 @@ const EPSILON: f32 = 0.0001;
|
|||||||
// E6: c3 * a3 = A1 * c1 * a1 + A2 * c2 * a2
|
// E6: c3 * a3 = A1 * c1 * a1 + A2 * c2 * a2
|
||||||
// E7: b * (1 - a3) = A1 * b * (1 - a1) + A2 * b * (1 - a2)
|
// E7: b * (1 - a3) = A1 * b * (1 - a1) + A2 * b * (1 - a2)
|
||||||
// dropping b from E7 and solving for a3
|
// dropping b from E7 and solving for a3
|
||||||
// E8: a3 = 1 - A1 * (1 - a1) + A2 * (1 - a2)
|
// E8: a3 = 1 - A1 * (1 - a1) - A2 * (1 - a2)
|
||||||
// we can now calculate the combined alpha value
|
// we can now calculate the combined alpha value
|
||||||
// and E6 can then be solved for c3
|
// and E6 can then be solved for c3
|
||||||
// E9: c3 = (A1 * c1 * a1 + A2 * c2 * a2) / a3
|
// E9: c3 = (A1 * c1 * a1 + A2 * c2 * a2) / a3
|
||||||
|
@ -14,7 +14,7 @@ pub enum SampleStrat {
|
|||||||
PixelCoverage,
|
PixelCoverage,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Transform {
|
pub struct Transform {
|
||||||
pub ori: Quaternion<f32>,
|
pub ori: Quaternion<f32>,
|
||||||
pub offset: Vec3<f32>,
|
pub offset: Vec3<f32>,
|
||||||
|
@ -8,6 +8,9 @@ use glyph_brush::GlyphBrushBuilder;
|
|||||||
use std::cell::{RefCell, RefMut};
|
use std::cell::{RefCell, RefMut};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
// TODO: probably make cache fields where we have mut getters into just public
|
||||||
|
// fields
|
||||||
|
|
||||||
// Multiplied by current window size
|
// Multiplied by current window size
|
||||||
const GLYPH_CACHE_SIZE: u32 = 1;
|
const GLYPH_CACHE_SIZE: u32 = 1;
|
||||||
// Glyph cache tolerances
|
// Glyph cache tolerances
|
||||||
@ -61,7 +64,9 @@ impl Cache {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn glyph_cache_tex(&self) -> &(Texture, UiTextureBindGroup) { &self.glyph_cache_tex }
|
pub fn glyph_cache_tex(&self) -> (&Texture, &UiTextureBindGroup) {
|
||||||
|
(&self.glyph_cache_tex.0, &self.glyph_cache_tex.1)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphBrush, &(Texture, UiTextureBindGroup)) {
|
pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphBrush, &(Texture, UiTextureBindGroup)) {
|
||||||
(self.glyph_brush.get_mut(), &self.glyph_cache_tex)
|
(self.glyph_brush.get_mut(), &self.glyph_cache_tex)
|
||||||
|
@ -533,7 +533,7 @@ impl IcedRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cache graphic at particular resolution.
|
// Cache graphic at particular resolution.
|
||||||
let (uv_aabr, tex_id) = match graphic_cache.cache_res(
|
let (uv_aabr, scale, tex_id) = match graphic_cache.cache_res(
|
||||||
renderer,
|
renderer,
|
||||||
pool,
|
pool,
|
||||||
graphic_id,
|
graphic_id,
|
||||||
@ -543,7 +543,7 @@ impl IcedRenderer {
|
|||||||
rotation,
|
rotation,
|
||||||
) {
|
) {
|
||||||
// TODO: get dims from graphic_cache (or have it return floats directly)
|
// TODO: get dims from graphic_cache (or have it return floats directly)
|
||||||
Some((aabr, tex_id)) => {
|
Some(((aabr, scale), tex_id)) => {
|
||||||
let cache_dims = graphic_cache
|
let cache_dims = graphic_cache
|
||||||
.get_tex(tex_id)
|
.get_tex(tex_id)
|
||||||
.0
|
.0
|
||||||
@ -552,7 +552,7 @@ impl IcedRenderer {
|
|||||||
.map(|e| e as f32);
|
.map(|e| e as f32);
|
||||||
let min = Vec2::new(aabr.min.x as f32, aabr.max.y as f32) / cache_dims;
|
let min = Vec2::new(aabr.min.x as f32, aabr.max.y as f32) / cache_dims;
|
||||||
let max = Vec2::new(aabr.max.x as f32, aabr.min.y as f32) / cache_dims;
|
let max = Vec2::new(aabr.max.x as f32, aabr.min.y as f32) / cache_dims;
|
||||||
(Aabr { min, max }, tex_id)
|
(Aabr { min, max }, scale, tex_id)
|
||||||
},
|
},
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
@ -562,7 +562,9 @@ impl IcedRenderer {
|
|||||||
self.switch_state(State::Image(tex_id));
|
self.switch_state(State::Image(tex_id));
|
||||||
|
|
||||||
self.mesh
|
self.mesh
|
||||||
.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, UiMode::Image));
|
.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, UiMode::Image {
|
||||||
|
scale,
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
Primitive::Gradient {
|
Primitive::Gradient {
|
||||||
bounds,
|
bounds,
|
||||||
@ -789,7 +791,7 @@ impl IcedRenderer {
|
|||||||
DrawKind::Image(tex_id) => self.cache.graphic_cache().get_tex(*tex_id),
|
DrawKind::Image(tex_id) => self.cache.graphic_cache().get_tex(*tex_id),
|
||||||
DrawKind::Plain => self.cache.glyph_cache_tex(),
|
DrawKind::Plain => self.cache.glyph_cache_tex(),
|
||||||
};
|
};
|
||||||
drawer.draw(&tex.1, verts.clone()); // Note: trivial clone
|
drawer.draw(tex.1, verts.clone()); // Note: trivial clone
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -854,7 +854,7 @@ impl Ui {
|
|||||||
srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into());
|
srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into());
|
||||||
|
|
||||||
// Cache graphic at particular resolution.
|
// Cache graphic at particular resolution.
|
||||||
let (uv_aabr, tex_id) = match graphic_cache.cache_res(
|
let (uv_aabr, scale, tex_id) = match graphic_cache.cache_res(
|
||||||
renderer,
|
renderer,
|
||||||
pool,
|
pool,
|
||||||
*graphic_id,
|
*graphic_id,
|
||||||
@ -863,7 +863,7 @@ impl Ui {
|
|||||||
*rotation,
|
*rotation,
|
||||||
) {
|
) {
|
||||||
// TODO: get dims from graphic_cache (or have it return floats directly)
|
// TODO: get dims from graphic_cache (or have it return floats directly)
|
||||||
Some((aabr, tex_id)) => {
|
Some(((aabr, scale), tex_id)) => {
|
||||||
let cache_dims = graphic_cache
|
let cache_dims = graphic_cache
|
||||||
.get_tex(tex_id)
|
.get_tex(tex_id)
|
||||||
.0
|
.0
|
||||||
@ -872,7 +872,7 @@ impl Ui {
|
|||||||
.map(|e| e as f32);
|
.map(|e| e as f32);
|
||||||
let min = Vec2::new(aabr.min.x as f32, aabr.max.y as f32) / cache_dims;
|
let min = Vec2::new(aabr.min.x as f32, aabr.max.y as f32) / cache_dims;
|
||||||
let max = Vec2::new(aabr.max.x as f32, aabr.min.y as f32) / cache_dims;
|
let max = Vec2::new(aabr.max.x as f32, aabr.min.y as f32) / cache_dims;
|
||||||
(Aabr { min, max }, tex_id)
|
(Aabr { min, max }, scale, tex_id)
|
||||||
},
|
},
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
@ -897,10 +897,10 @@ impl Ui {
|
|||||||
|
|
||||||
mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, match *rotation {
|
mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, match *rotation {
|
||||||
Rotation::None | Rotation::Cw90 | Rotation::Cw180 | Rotation::Cw270 => {
|
Rotation::None | Rotation::Cw90 | Rotation::Cw180 | Rotation::Cw270 => {
|
||||||
UiMode::Image
|
UiMode::Image { scale }
|
||||||
},
|
},
|
||||||
Rotation::SourceNorth => UiMode::ImageSourceNorth,
|
Rotation::SourceNorth => UiMode::ImageSourceNorth { scale },
|
||||||
Rotation::TargetNorth => UiMode::ImageTargetNorth,
|
Rotation::TargetNorth => UiMode::ImageTargetNorth { scale },
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
PrimitiveKind::Text { .. } => {
|
PrimitiveKind::Text { .. } => {
|
||||||
@ -1073,7 +1073,7 @@ impl Ui {
|
|||||||
DrawKind::Image(tex_id) => self.cache.graphic_cache().get_tex(*tex_id),
|
DrawKind::Image(tex_id) => self.cache.graphic_cache().get_tex(*tex_id),
|
||||||
DrawKind::Plain => self.cache.glyph_cache_tex(),
|
DrawKind::Plain => self.cache.glyph_cache_tex(),
|
||||||
};
|
};
|
||||||
drawer.draw(&tex.1, verts.clone()); // Note: trivial clone
|
drawer.draw(tex.1, verts.clone()); // Note: trivial clone
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user