mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
init (scaling UI images during sampling on the GPU instead of on the CPU)
This commit is contained in:
parent
564530f5e2
commit
d62bf8a790
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.
@ -1,31 +1,215 @@
|
||||
#version 420 core
|
||||
|
||||
#include <globals.glsl>
|
||||
#include <constants.glsl>
|
||||
|
||||
layout(location = 0) in vec2 f_uv;
|
||||
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)
|
||||
uniform u_locals {
|
||||
vec4 w_pos;
|
||||
};
|
||||
|
||||
// TODO: swap with u_locals because that may change more frequently?
|
||||
layout(set = 2, binding = 0)
|
||||
uniform texture2D t_tex;
|
||||
layout(set = 2, binding = 1)
|
||||
uniform sampler s_tex;
|
||||
layout (std140, set = 2, binding = 2)
|
||||
uniform tex_locals {
|
||||
uvec2 texture_size;
|
||||
};
|
||||
|
||||
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 of just sampling a 1 pixel length from the
|
||||
// center on each side. An alternative would be to pre-compute mipmap
|
||||
// levels that could be sampled from.
|
||||
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 = 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;
|
||||
uv0 = uv_pixel / texture_size;
|
||||
uv1 = uv_pixel / texture_size;
|
||||
uv2 = uv_pixel / texture_size;
|
||||
uv3 = uv_pixel / 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 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() {
|
||||
// Text
|
||||
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
|
||||
// HACK: bit 0 is set for both ordinary and north-facing images.
|
||||
} 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.
|
||||
|
||||
// TODO: benchmark before and after by viewing zoomed out map in the UI
|
||||
// (at particular ui scale and map zoom out, far enough to trigger downscaling)
|
||||
// (seems like ~10% increase if any)
|
||||
|
||||
// Convert to sampled pixel coordinates.
|
||||
vec2 uv_pixel = f_uv * texture_size;
|
||||
vec4 image_color = vec4(1.0, 0, 0, 1);
|
||||
#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
|
||||
|
||||
tgt_color = f_color * image_color;
|
||||
// 2D Geometry
|
||||
} else if (f_mode == uint(2)) {
|
||||
tgt_color = f_color;
|
||||
|
@ -6,7 +6,8 @@ layout(location = 0) in vec2 v_pos;
|
||||
layout(location = 1) in vec2 v_uv;
|
||||
layout(location = 2) in vec4 v_color;
|
||||
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)
|
||||
uniform u_locals {
|
||||
@ -17,10 +18,15 @@ layout(set = 2, binding = 0)
|
||||
uniform texture2D t_tex;
|
||||
layout(set = 2, binding = 1)
|
||||
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 = 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() {
|
||||
f_color = v_color;
|
||||
@ -39,7 +45,7 @@ void main() {
|
||||
gl_Position = vec4(v_pos, 0.5, 1.0);
|
||||
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).
|
||||
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);
|
||||
vec2 v_centered = (v_uv - v_center) / aspect_ratio;
|
||||
vec2 v_rotated = look_at * v_centered;
|
||||
@ -60,5 +66,6 @@ void main() {
|
||||
gl_Position = vec4(v_pos, 0.5, 1.0);
|
||||
}
|
||||
|
||||
f_scale = v_scale;
|
||||
f_mode = v_mode;
|
||||
}
|
||||
|
@ -536,4 +536,7 @@ pub enum ExperimentalShader {
|
||||
SmearReflections,
|
||||
/// Apply the point shadows from cheap shadows on top of shadow mapping.
|
||||
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,
|
||||
}
|
||||
|
@ -10,12 +10,17 @@ pub struct Vertex {
|
||||
uv: [f32; 2],
|
||||
color: [f32; 4],
|
||||
center: [f32; 2],
|
||||
// Used calculating where to sample scaled images.
|
||||
scale: [f32; 2],
|
||||
mode: u32,
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
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 {
|
||||
array_stride: Self::STRIDE,
|
||||
step_mode: wgpu::InputStepMode::Vertex,
|
||||
@ -47,6 +52,20 @@ impl Default for Locals {
|
||||
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.
|
||||
pub const MODE_TEXT: u32 = 0;
|
||||
/// Draw an image from the texture at `tex` in the fragment shader.
|
||||
@ -64,22 +83,44 @@ pub const MODE_IMAGE_SOURCE_NORTH: u32 = 3;
|
||||
/// FIXME: Make more principled.
|
||||
pub const MODE_IMAGE_TARGET_NORTH: u32 = 5;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Mode {
|
||||
Text,
|
||||
Image,
|
||||
Image {
|
||||
scale: Vec2<f32>,
|
||||
},
|
||||
Geometry,
|
||||
ImageSourceNorth,
|
||||
ImageTargetNorth,
|
||||
/// Draw an image from the texture at `tex` in the fragment shader, with the
|
||||
/// 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 {
|
||||
fn value(self) -> u32 {
|
||||
match self {
|
||||
Mode::Text => MODE_TEXT,
|
||||
Mode::Image => MODE_IMAGE,
|
||||
Mode::Image { .. } => MODE_IMAGE,
|
||||
Mode::Geometry => MODE_GEOMETRY,
|
||||
Mode::ImageSourceNorth => MODE_IMAGE_SOURCE_NORTH,
|
||||
Mode::ImageTargetNorth => MODE_IMAGE_TARGET_NORTH,
|
||||
Mode::ImageSourceNorth { .. } => MODE_IMAGE_SOURCE_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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,6 +178,17 @@ impl UiLayout {
|
||||
},
|
||||
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 +210,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 {
|
||||
label: None,
|
||||
layout: &self.texture,
|
||||
@ -171,6 +228,10 @@ impl UiLayout {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&texture.sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: tex_locals.buf().as_entire_binding(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@ -268,17 +329,19 @@ pub fn create_quad_vert_gradient(
|
||||
let top_color = top_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()
|
||||
} else {
|
||||
rect.center().into_array()
|
||||
};
|
||||
let scale = mode.scale().into_array();
|
||||
let mode_val = mode.value();
|
||||
let v = |pos, uv, color| Vertex {
|
||||
pos,
|
||||
uv,
|
||||
center,
|
||||
color,
|
||||
scale,
|
||||
mode: mode_val,
|
||||
};
|
||||
let aabr_to_lbrt = |aabr: Aabr<f32>| (aabr.min.x, aabr.min.y, aabr.max.x, aabr.max.y);
|
||||
@ -315,12 +378,14 @@ pub fn create_tri(
|
||||
mode: Mode,
|
||||
) -> Tri<Vertex> {
|
||||
let center = [0.0, 0.0];
|
||||
let scale = mode.scale().into_array();
|
||||
let mode_val = mode.value();
|
||||
let v = |pos, uv| Vertex {
|
||||
pos,
|
||||
uv,
|
||||
center,
|
||||
color: color.into_array(),
|
||||
scale,
|
||||
mode: mode_val,
|
||||
};
|
||||
Tri::new(
|
||||
|
@ -47,8 +47,12 @@ impl Renderer {
|
||||
self.layouts.ui.bind_locals(&self.device, locals)
|
||||
}
|
||||
|
||||
pub fn ui_bind_texture(&self, texture: &Texture) -> ui::TextureBindGroup {
|
||||
self.layouts.ui.bind_texture(&self.device, texture)
|
||||
pub fn ui_bind_texture(&mut self, texture: &Texture) -> ui::TextureBindGroup {
|
||||
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(
|
||||
|
@ -136,8 +136,8 @@ impl Texture {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -61,7 +61,6 @@ pub struct Id(u32);
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub struct TexId(usize);
|
||||
|
||||
type Parameters = (Id, Vec2<u16>);
|
||||
// TODO replace with slab/slotmap
|
||||
type GraphicMap = HashMap<Id, Graphic>;
|
||||
|
||||
@ -93,7 +92,7 @@ impl CachedDetails {
|
||||
fn info(
|
||||
&self,
|
||||
atlases: &[(SimpleAtlasAllocator, usize)],
|
||||
dims: Vec2<u16>,
|
||||
textures: &Slab<(Texture, UiTextureBindGroup)>,
|
||||
) -> (usize, bool, Aabr<u16>) {
|
||||
match *self {
|
||||
CachedDetails::Atlas {
|
||||
@ -105,14 +104,16 @@ impl CachedDetails {
|
||||
(index, valid, Aabr {
|
||||
min: Vec2::zero(),
|
||||
// Note texture should always match the cached dimensions
|
||||
max: dims,
|
||||
// TODO: Justify cast here?
|
||||
max: textures[index].0.get_dimensions().xy().map(|e| e as u16),
|
||||
})
|
||||
},
|
||||
CachedDetails::Immutable { index } => {
|
||||
(index, true, Aabr {
|
||||
min: Vec2::zero(),
|
||||
// Note texture should always match the cached dimensions
|
||||
max: dims,
|
||||
// TODO: Justify cast here?
|
||||
max: textures[index].0.get_dimensions().xy().map(|e| e as u16),
|
||||
})
|
||||
},
|
||||
}
|
||||
@ -147,7 +148,7 @@ pub struct GraphicCache {
|
||||
atlases: Vec<(SimpleAtlasAllocator, usize)>,
|
||||
textures: Slab<(Texture, UiTextureBindGroup)>,
|
||||
// Stores the location of graphics rendered at a particular resolution and cached on the cpu
|
||||
cache_map: HashMap<Parameters, CachedDetails>,
|
||||
cache_map: HashMap<Id, CachedDetails>,
|
||||
|
||||
keyed_jobs: KeyedJobs<(Id, Vec2<u16>), Option<(RgbaImage, Option<Rgba<f32>>)>>,
|
||||
}
|
||||
@ -237,17 +238,23 @@ impl GraphicCache {
|
||||
renderer: &mut Renderer,
|
||||
pool: Option<&SlowJobPool>,
|
||||
graphic_id: Id,
|
||||
// TODO: if we aren't resizing here we can upload image earlier... (as long as this doesn't
|
||||
// lead to uploading too much unused stuff). (cache_res name invalid)
|
||||
dims: Vec2<u16>,
|
||||
source: Aabr<f64>,
|
||||
rotation: Rotation,
|
||||
) -> Option<(Aabr<f64>, TexId)> {
|
||||
) -> Option<((Aabr<f64>, Vec2<f32>), TexId)> {
|
||||
let dims = match rotation {
|
||||
// The image is placed into the atlas with no rotation, so we need to swap the
|
||||
// dimensions here to get the resolution that the image will be displayed at but
|
||||
// re-oriented into the "upright" space that the image is stored in and sampled from
|
||||
// (this can be bit confusing initially / hard to explain).
|
||||
Rotation::Cw90 | Rotation::Cw270 => Vec2::new(dims.y, dims.x),
|
||||
Rotation::None | Rotation::Cw180 => dims,
|
||||
Rotation::SourceNorth => dims,
|
||||
Rotation::TargetNorth => dims,
|
||||
};
|
||||
let key = (graphic_id, dims);
|
||||
let key = graphic_id;
|
||||
|
||||
// Rotate aabr according to requested rotation.
|
||||
let rotated_aabr = |Aabr { min, max }| match rotation {
|
||||
@ -264,7 +271,7 @@ impl GraphicCache {
|
||||
};
|
||||
// Scale aabr according to provided source rectangle.
|
||||
let scaled_aabr = |aabr: Aabr<_>| {
|
||||
let size: Vec2<_> = aabr.size().into();
|
||||
let size: Vec2<f64> = aabr.size().into();
|
||||
Aabr {
|
||||
min: size.mul_add(source.min, aabr.min),
|
||||
max: size.mul_add(source.max, aabr.min),
|
||||
@ -272,7 +279,19 @@ impl GraphicCache {
|
||||
};
|
||||
// Apply all transformations.
|
||||
// TODO: Verify rotation is being applied correctly.
|
||||
let transformed_aabr = |aabr| rotated_aabr(scaled_aabr(aabr));
|
||||
let transformed_aabr = |aabr| {
|
||||
let scaled = scaled_aabr(aabr);
|
||||
// Calculate how many displayed pixels there are for each pixel in the source
|
||||
// image. We need this to calculate where to sample in the shader to
|
||||
// retain crisp pixel borders when scaling the image.
|
||||
// TODO: A bit hacky inserting this here, just to get things working initially
|
||||
let scale = dims.map2(
|
||||
Vec2::from(scaled.size()),
|
||||
|screen_pixels, sample_pixels: f64| screen_pixels as f32 / sample_pixels as f32,
|
||||
);
|
||||
let transformed = rotated_aabr(scaled);
|
||||
(transformed, scale)
|
||||
};
|
||||
|
||||
let Self {
|
||||
textures,
|
||||
@ -285,7 +304,7 @@ impl GraphicCache {
|
||||
let details = match cache_map.entry(key) {
|
||||
Entry::Occupied(details) => {
|
||||
let details = details.get();
|
||||
let (idx, valid, aabr) = details.info(atlases, dims);
|
||||
let (idx, valid, aabr) = details.info(atlases, textures);
|
||||
|
||||
// Check if the cached version has been invalidated by replacing the underlying
|
||||
// graphic
|
||||
@ -312,28 +331,33 @@ impl GraphicCache {
|
||||
let (image, border_color) =
|
||||
draw_graphic(graphic_map, graphic_id, dims, &mut self.keyed_jobs, pool)?;
|
||||
|
||||
// TODO: justify cast?
|
||||
let image_dims = Vec2::<u32>::from(image.dimensions()).map(|e| e as u16);
|
||||
|
||||
// Upload
|
||||
let atlas_size = atlas_size(renderer);
|
||||
|
||||
// Allocate space on the gpu
|
||||
// Check size of graphic
|
||||
// Graphics over a particular size are sent to their own textures
|
||||
// Allocate space on the gpu.
|
||||
//
|
||||
// Graphics with a border color.
|
||||
let location = if let Some(border_color) = border_color {
|
||||
// Create a new immutable texture.
|
||||
let texture = create_image(renderer, image, border_color);
|
||||
// NOTE: All mutations happen only after the upload succeeds!
|
||||
let index = textures.insert(texture);
|
||||
CachedDetails::Immutable { index }
|
||||
// Graphics over a particular size compared to the atlas size are sent
|
||||
// to their own textures. Here we check for ones under that
|
||||
// size.
|
||||
} else if atlas_size
|
||||
.map2(dims, |a, d| a as f32 * ATLAS_CUTOFF_FRAC >= d as f32)
|
||||
.map2(image_dims, |a, d| a as f32 * ATLAS_CUTOFF_FRAC >= d as f32)
|
||||
.reduce_and()
|
||||
{
|
||||
// Fit into an atlas
|
||||
let mut loc = None;
|
||||
for (atlas_idx, &mut (ref mut atlas, texture_idx)) in atlases.iter_mut().enumerate() {
|
||||
let dims = dims.map(|e| e.max(1));
|
||||
if let Some(rectangle) = atlas.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
|
||||
{
|
||||
let clamped_dims = image_dims.map(|e| i32::from(e.max(1)));
|
||||
if let Some(rectangle) = atlas.allocate(size2(clamped_dims.x, clamped_dims.y)) {
|
||||
let aabr = aabr_from_alloc_rect(rectangle);
|
||||
loc = Some(CachedDetails::Atlas {
|
||||
atlas_idx,
|
||||
@ -350,9 +374,9 @@ impl GraphicCache {
|
||||
// Create a new atlas
|
||||
None => {
|
||||
let (mut atlas, texture) = create_atlas_texture(renderer);
|
||||
let dims = dims.map(|e| e.max(1));
|
||||
let clamped_dims = image_dims.map(|e| i32::from(e.max(1)));
|
||||
let aabr = atlas
|
||||
.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
|
||||
.allocate(size2(clamped_dims.x, clamped_dims.y))
|
||||
.map(aabr_from_alloc_rect)
|
||||
.unwrap();
|
||||
// NOTE: All mutations happen only after the texture creation succeeds!
|
||||
@ -370,7 +394,7 @@ impl GraphicCache {
|
||||
} else {
|
||||
// Create a texture just for this
|
||||
let texture = {
|
||||
let tex = renderer.create_dynamic_texture(dims.map(|e| e as u32));
|
||||
let tex = renderer.create_dynamic_texture(image_dims.map(u32::from));
|
||||
let bind = renderer.ui_bind_texture(&tex);
|
||||
(tex, bind)
|
||||
};
|
||||
@ -381,7 +405,7 @@ impl GraphicCache {
|
||||
Aabr {
|
||||
min: Vec2::zero(),
|
||||
// Note texture should always match the cached dimensions
|
||||
max: dims,
|
||||
max: image_dims,
|
||||
},
|
||||
&textures[index].0,
|
||||
&image,
|
||||
@ -390,7 +414,7 @@ impl GraphicCache {
|
||||
};
|
||||
|
||||
// Extract information from cache entry.
|
||||
let (idx, _, aabr) = location.info(atlases, dims);
|
||||
let (idx, _, aabr) = location.info(atlases, textures);
|
||||
|
||||
// Insert into cached map
|
||||
details.insert(location);
|
||||
@ -419,14 +443,17 @@ fn draw_graphic(
|
||||
// Render image at requested resolution
|
||||
// TODO: Use source aabr.
|
||||
Graphic::Image(ref image, border_color) => Some((
|
||||
resize_pixel_art(
|
||||
/*resize_pixel_art(
|
||||
&image.to_rgba8(),
|
||||
u32::from(dims.x),
|
||||
u32::from(dims.y),
|
||||
),
|
||||
),*/
|
||||
image.to_rgba8(),
|
||||
border_color,
|
||||
)),
|
||||
Graphic::Voxel(ref segment, trans, sample_strat) => {
|
||||
// TODO: how to decide on dimensions to render voxel models to?
|
||||
// (with resizing being done in shaders)
|
||||
Some((renderer::draw_vox(segment, dims, trans, sample_strat), None))
|
||||
},
|
||||
Graphic::Blank => None,
|
||||
@ -498,12 +525,12 @@ fn create_image(
|
||||
let tex = renderer
|
||||
.create_texture(
|
||||
&DynamicImage::ImageRgba8(image),
|
||||
None,
|
||||
//TODO: either use the desktop only border color or just emulate this
|
||||
Some(wgpu::FilterMode::Linear),
|
||||
// TODO: either use the desktop only border color or just emulate this
|
||||
// Some(border_color.into_array().into()),
|
||||
Some(wgpu::AddressMode::ClampToBorder),
|
||||
)
|
||||
.expect("create_texture only panics is non ImageRbga8 is passed");
|
||||
.expect("create_texture only panics if non ImageRbga8 is passed");
|
||||
let bind = renderer.ui_bind_texture(&tex);
|
||||
|
||||
(tex, bind)
|
||||
|
@ -533,7 +533,7 @@ impl IcedRenderer {
|
||||
}
|
||||
|
||||
// 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,
|
||||
pool,
|
||||
graphic_id,
|
||||
@ -543,7 +543,7 @@ impl IcedRenderer {
|
||||
rotation,
|
||||
) {
|
||||
// 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
|
||||
.get_tex(tex_id)
|
||||
.0
|
||||
@ -552,7 +552,7 @@ impl IcedRenderer {
|
||||
.map(|e| e as f32);
|
||||
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;
|
||||
(Aabr { min, max }, tex_id)
|
||||
(Aabr { min, max }, scale, tex_id)
|
||||
},
|
||||
None => return,
|
||||
};
|
||||
@ -562,7 +562,9 @@ impl IcedRenderer {
|
||||
self.switch_state(State::Image(tex_id));
|
||||
|
||||
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 {
|
||||
bounds,
|
||||
|
@ -854,7 +854,7 @@ impl Ui {
|
||||
srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into());
|
||||
|
||||
// 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,
|
||||
pool,
|
||||
*graphic_id,
|
||||
@ -863,7 +863,7 @@ impl Ui {
|
||||
*rotation,
|
||||
) {
|
||||
// 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
|
||||
.get_tex(tex_id)
|
||||
.0
|
||||
@ -872,7 +872,7 @@ impl Ui {
|
||||
.map(|e| e as f32);
|
||||
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;
|
||||
(Aabr { min, max }, tex_id)
|
||||
(Aabr { min, max }, scale, tex_id)
|
||||
},
|
||||
None => continue,
|
||||
};
|
||||
@ -897,10 +897,10 @@ impl Ui {
|
||||
|
||||
mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, match *rotation {
|
||||
Rotation::None | Rotation::Cw90 | Rotation::Cw180 | Rotation::Cw270 => {
|
||||
UiMode::Image
|
||||
UiMode::Image { scale }
|
||||
},
|
||||
Rotation::SourceNorth => UiMode::ImageSourceNorth,
|
||||
Rotation::TargetNorth => UiMode::ImageTargetNorth,
|
||||
Rotation::SourceNorth => UiMode::ImageSourceNorth { scale },
|
||||
Rotation::TargetNorth => UiMode::ImageTargetNorth { scale },
|
||||
}));
|
||||
},
|
||||
PrimitiveKind::Text { .. } => {
|
||||
|
Loading…
Reference in New Issue
Block a user