init (scaling UI images during sampling on the GPU instead of on the CPU)

This commit is contained in:
Imbris 2022-08-29 00:42:14 -04:00
parent 564530f5e2
commit d62bf8a790
11 changed files with 352 additions and 60 deletions

Binary file not shown.

Binary file not shown.

View File

@ -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;

View File

@ -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;
}

View File

@ -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,
}

View File

@ -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(

View File

@ -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(

View File

@ -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()
};

View File

@ -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)

View File

@ -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,

View File

@ -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 { .. } => {