Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use mipmaps and trilinear filtering for large, standalone textures. #2408

Merged
merged 1 commit into from Feb 12, 2018
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Use mipmaps and trilinear filtering for large, standalone textures.

Images that are large (currently this means one dimension > 512) are
placed into standalone textures rather than allocated into the
shared texture cache pages. Enabling mipmaps and trilinear filtering
on this subset of images is very simple, and also fixes the majority
of images where lack of mipmaps are a noticeable quality issue.

In the future, we can expand this to support mipmaps within the
shared cache pages, if desired, by manually drawing the mips with
a shader and making use of the mip-bias parameter to disable mip
selection for shared cache regions without mips.

Fixes #2023.
  • Loading branch information
gw3583 committed Feb 12, 2018
commit 19f43d91dbb5f192fe82cfd486717f5d9752cb49
@@ -70,6 +70,7 @@ pub enum DepthFunction {
pub enum TextureFilter {
Nearest,
Linear,
Trilinear,
}

#[derive(Debug)]
@@ -924,7 +925,9 @@ impl Device {
}

pub fn create_texture(
&mut self, target: TextureTarget, format: ImageFormat,
&mut self,
target: TextureTarget,
format: ImageFormat,
) -> Texture {
Texture {
id: self.gl.gen_textures(1)[0],
@@ -941,15 +944,21 @@ impl Device {
}

fn set_texture_parameters(&mut self, target: gl::GLuint, filter: TextureFilter) {
let filter = match filter {
let mag_filter = match filter {
TextureFilter::Nearest => gl::NEAREST,
TextureFilter::Linear | TextureFilter::Trilinear => gl::LINEAR,
};

let min_filter = match filter {
TextureFilter::Nearest => gl::NEAREST,
TextureFilter::Linear => gl::LINEAR,
TextureFilter::Trilinear => gl::LINEAR_MIPMAP_LINEAR,
};

self.gl
.tex_parameter_i(target, gl::TEXTURE_MAG_FILTER, filter as gl::GLint);
.tex_parameter_i(target, gl::TEXTURE_MAG_FILTER, mag_filter as gl::GLint);
self.gl
.tex_parameter_i(target, gl::TEXTURE_MIN_FILTER, filter as gl::GLint);
.tex_parameter_i(target, gl::TEXTURE_MIN_FILTER, min_filter as gl::GLint);

self.gl
.tex_parameter_i(target, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint);
@@ -2229,6 +2238,11 @@ impl<'a> UploadTarget<'a> {
_ => panic!("BUG: Unexpected texture target!"),
}

// If using tri-linear filtering, build the mip-map chain for this texture.
if self.texture.filter == TextureFilter::Trilinear {
self.gl.generate_mipmap(self.texture.target);
}

// Reset row length to 0, otherwise the stride would apply to all texture uploads.
if chunk.stride.is_some() {
self.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, 0 as _);
@@ -857,11 +857,6 @@ impl ResourceCache {
}
};

let filter = match request.rendering {
ImageRendering::Pixelated => TextureFilter::Nearest,
ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear,
};

let descriptor = if let Some(tile) = request.tile {
let tile_size = image_template.tiling.unwrap();
let image_descriptor = &image_template.descriptor;
@@ -897,6 +892,29 @@ impl ResourceCache {
image_template.descriptor.clone()
};

let filter = match request.rendering {
ImageRendering::Pixelated => {
TextureFilter::Nearest
}
ImageRendering::Auto | ImageRendering::CrispEdges => {
// If the texture uses linear filtering, enable mipmaps and
// trilinear filtering, for better image quality. We only
// support this for now on textures that are not placed
// into the shared cache. This accounts for any image
// that is > 512 in either dimension, so it should cover
// the most important use cases. We may want to support
// mip-maps on shared cache items in the future.
if !self.texture_cache.is_allowed_in_shared_cache(
TextureFilter::Linear,
&descriptor,
) {
TextureFilter::Trilinear
} else {
TextureFilter::Linear
}
}
};

let entry = self.cached_images.get_mut(&request).as_mut().unwrap();
self.texture_cache.update(
&mut entry.texture_cache_handle,
@@ -384,7 +384,9 @@ impl TextureCache {
(ImageFormat::BGRA8, TextureFilter::Nearest) => &mut self.array_rgba8_nearest,
(ImageFormat::RGBAF32, _) |
(ImageFormat::RG8, _) |
(ImageFormat::R8, TextureFilter::Nearest) => unreachable!(),
(ImageFormat::R8, TextureFilter::Nearest) |
(ImageFormat::R8, TextureFilter::Trilinear) |
(ImageFormat::BGRA8, TextureFilter::Trilinear) => unreachable!(),
};

&mut texture_array.regions[region_index as usize]
@@ -605,6 +607,8 @@ impl TextureCache {
(ImageFormat::BGRA8, TextureFilter::Nearest) => &mut self.array_rgba8_nearest,
(ImageFormat::RGBAF32, _) |
(ImageFormat::R8, TextureFilter::Nearest) |
(ImageFormat::R8, TextureFilter::Trilinear) |
(ImageFormat::BGRA8, TextureFilter::Trilinear) |
(ImageFormat::RG8, _) => unreachable!(),
};

@@ -645,6 +649,34 @@ impl TextureCache {
)
}

// Returns true if the given image descriptor *may* be
// placed in the shared texture cache.
pub fn is_allowed_in_shared_cache(
&self,
filter: TextureFilter,
descriptor: &ImageDescriptor,
) -> bool {
let mut allowed_in_shared_cache = true;

// TODO(gw): For now, anything that requests nearest filtering and isn't BGRA8
// just fails to allocate in a texture page, and gets a standalone
// texture. This is probably rare enough that it can be fixed up later.
if filter == TextureFilter::Nearest &&
descriptor.format != ImageFormat::BGRA8 {
allowed_in_shared_cache = false;
}

// Anything larger than 512 goes in a standalone texture.
// TODO(gw): If we find pages that suffer from batch breaks in this
// case, add support for storing these in a standalone
// texture array.
if descriptor.width > 512 || descriptor.height > 512 {
allowed_in_shared_cache = false;
}

allowed_in_shared_cache
}

// Allocate storage for a given image. This attempts to allocate
// from the shared cache, but falls back to standalone texture
// if the image is too large, or the cache is full.
@@ -658,27 +690,15 @@ impl TextureCache {
assert!(descriptor.width > 0 && descriptor.height > 0);

// Work out if this image qualifies to go in the shared (batching) cache.
let mut allowed_in_shared_cache = true;
let allowed_in_shared_cache = self.is_allowed_in_shared_cache(
filter,
&descriptor,
);
let mut allocated_in_shared_cache = true;
let mut new_cache_entry = None;
let size = DeviceUintSize::new(descriptor.width, descriptor.height);
let frame_id = self.frame_id;

// TODO(gw): For now, anything that requests nearest filtering and isn't BGRA8
// just fails to allocate in a texture page, and gets a standalone
// texture. This is probably rare enough that it can be fixed up later.
if filter == TextureFilter::Nearest && descriptor.format != ImageFormat::BGRA8 {
allowed_in_shared_cache = false;
}

// Anything larger than 512 goes in a standalone texture.
// TODO(gw): If we find pages that suffer from batch breaks in this
// case, add support for storing these in a standalone
// texture array.
if descriptor.width > 512 || descriptor.height > 512 {
allowed_in_shared_cache = false;
}

// If it's allowed in the cache, see if there is a spot for it.
if allowed_in_shared_cache {
new_cache_entry = self.allocate_from_shared_cache(
Binary file not shown.
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.