From 478008dcf079656eb93f70bec26ec846b9ea2bad Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 4 May 2020 13:26:40 -0700 Subject: [PATCH] Use all four channels in the mask texture. Each bundle of four pixels on a scanline is packed into the RGBA channels of the mask texture. The area LUT is also expanded to be RGBA so that four pixels' worth of areas can be looked up at once. Nice improvement on `paris-30k` from MPVG. Closes #262. --- demo/common/src/ui.rs | 33 +++-- gpu/src/lib.rs | 25 +++- renderer/src/gpu/renderer.rs | 25 ++-- renderer/src/gpu/shaders.rs | 11 +- resources/shaders/gl3/fill.fs.glsl | 6 +- resources/shaders/gl3/fill.vs.glsl | 12 +- resources/shaders/gl3/tile.fs.glsl | 14 ++- resources/shaders/gl3/tile.vs.glsl | 2 +- resources/shaders/gl3/tile_clip.fs.glsl | 3 +- resources/shaders/metal/fill.cs.metal | 32 +++-- resources/shaders/metal/fill.fs.metal | 6 +- resources/shaders/metal/fill.vs.metal | 8 +- resources/shaders/metal/tile.fs.metal | 139 +++++++++++---------- resources/shaders/metal/tile.vs.metal | 2 +- resources/shaders/metal/tile_clip.fs.metal | 3 +- resources/textures/area-lut.png | Bin 5612 -> 35027 bytes shaders/fill.cs.glsl | 19 ++- shaders/fill.fs.glsl | 2 +- shaders/fill.inc.glsl | 4 +- shaders/fill.vs.glsl | 12 +- shaders/tile.fs.glsl | 14 ++- shaders/tile.vs.glsl | 2 +- shaders/tile_clip.fs.glsl | 3 +- ui/src/lib.rs | 15 ++- utils/area-lut/src/main.rs | 85 +++++++------ 25 files changed, 276 insertions(+), 201 deletions(-) diff --git a/demo/common/src/ui.rs b/demo/common/src/ui.rs index 8538e2b9..2c29d9e2 100644 --- a/demo/common/src/ui.rs +++ b/demo/common/src/ui.rs @@ -14,7 +14,7 @@ use crate::{BackgroundColor, Options}; use pathfinder_color::ColorU; use pathfinder_geometry::rect::RectI; use pathfinder_geometry::vector::{Vector2I, vec2i}; -use pathfinder_gpu::Device; +use pathfinder_gpu::{Device, TextureFormat}; use pathfinder_renderer::gpu::debug::DebugUIPresenter; use pathfinder_resources::ResourceLoader; use pathfinder_ui::{BUTTON_HEIGHT, BUTTON_TEXT_OFFSET, BUTTON_WIDTH, FONT_ASCENT, PADDING}; @@ -121,15 +121,30 @@ where D: Device, { pub fn new(device: &D, resources: &dyn ResourceLoader) -> DemoUIPresenter { - let effects_texture = device.create_texture_from_png(resources, EFFECTS_PNG_NAME); - let open_texture = device.create_texture_from_png(resources, OPEN_PNG_NAME); - let rotate_texture = device.create_texture_from_png(resources, ROTATE_PNG_NAME); - let zoom_in_texture = device.create_texture_from_png(resources, ZOOM_IN_PNG_NAME); + let effects_texture = device.create_texture_from_png(resources, + EFFECTS_PNG_NAME, + TextureFormat::R8); + let open_texture = device.create_texture_from_png(resources, + OPEN_PNG_NAME, + TextureFormat::R8); + let rotate_texture = device.create_texture_from_png(resources, + ROTATE_PNG_NAME, + TextureFormat::R8); + let zoom_in_texture = device.create_texture_from_png(resources, + ZOOM_IN_PNG_NAME, + TextureFormat::R8); let zoom_actual_size_texture = device.create_texture_from_png(resources, - ZOOM_ACTUAL_SIZE_PNG_NAME); - let zoom_out_texture = device.create_texture_from_png(resources, ZOOM_OUT_PNG_NAME); - let background_texture = device.create_texture_from_png(resources, BACKGROUND_PNG_NAME); - let screenshot_texture = device.create_texture_from_png(resources, SCREENSHOT_PNG_NAME); + ZOOM_ACTUAL_SIZE_PNG_NAME, + TextureFormat::R8); + let zoom_out_texture = device.create_texture_from_png(resources, + ZOOM_OUT_PNG_NAME, + TextureFormat::R8); + let background_texture = device.create_texture_from_png(resources, + BACKGROUND_PNG_NAME, + TextureFormat::R8); + let screenshot_texture = device.create_texture_from_png(resources, + SCREENSHOT_PNG_NAME, + TextureFormat::R8); DemoUIPresenter { effects_texture, diff --git a/gpu/src/lib.rs b/gpu/src/lib.rs index 47009731..bc578620 100644 --- a/gpu/src/lib.rs +++ b/gpu/src/lib.rs @@ -104,13 +104,26 @@ pub trait Device: Sized { fn try_recv_texture_data(&self, receiver: &Self::TextureDataReceiver) -> Option; fn recv_texture_data(&self, receiver: &Self::TextureDataReceiver) -> TextureData; - fn create_texture_from_png(&self, resources: &dyn ResourceLoader, name: &str) -> Self::Texture { + fn create_texture_from_png(&self, + resources: &dyn ResourceLoader, + name: &str, + format: TextureFormat) + -> Self::Texture { let data = resources.slurp(&format!("textures/{}.png", name)).unwrap(); - let image = image::load_from_memory_with_format(&data, ImageFormat::Png) - .unwrap() - .to_luma(); - let size = vec2i(image.width() as i32, image.height() as i32); - self.create_texture_from_data(TextureFormat::R8, size, TextureDataRef::U8(&image)) + let image = image::load_from_memory_with_format(&data, ImageFormat::Png).unwrap(); + match format { + TextureFormat::R8 => { + let image = image.to_luma(); + let size = vec2i(image.width() as i32, image.height() as i32); + self.create_texture_from_data(format, size, TextureDataRef::U8(&image)) + } + TextureFormat::RGBA8 => { + let image = image.to_rgba(); + let size = vec2i(image.width() as i32, image.height() as i32); + self.create_texture_from_data(format, size, TextureDataRef::U8(&image)) + } + _ => unimplemented!(), + } } fn create_program_from_shader_names( diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 15b20cdf..a96a6767 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -65,8 +65,8 @@ const TEXTURE_METADATA_TEXTURE_WIDTH: i32 = TEXTURE_METADATA_ENTRIES_PER_ROW * const TEXTURE_METADATA_TEXTURE_HEIGHT: i32 = 65536 / TEXTURE_METADATA_ENTRIES_PER_ROW; // FIXME(pcwalton): Shrink this again! -const MASK_FRAMEBUFFER_WIDTH: i32 = TILE_WIDTH as i32 * MASK_TILES_ACROSS as i32; -const MASK_FRAMEBUFFER_HEIGHT: i32 = TILE_HEIGHT as i32 * MASK_TILES_DOWN as i32; +const MASK_FRAMEBUFFER_WIDTH: i32 = TILE_WIDTH as i32 * MASK_TILES_ACROSS as i32; +const MASK_FRAMEBUFFER_HEIGHT: i32 = TILE_HEIGHT as i32 / 4 * MASK_TILES_DOWN as i32; const COMBINER_CTRL_COLOR_COMBINE_SRC_IN: i32 = 0x1; const COMBINER_CTRL_COLOR_COMBINE_DEST_IN: i32 = 0x2; @@ -170,8 +170,10 @@ impl Renderer where D: Device { let stencil_program = StencilProgram::new(&device, resources); let reprojection_program = ReprojectionProgram::new(&device, resources); - let area_lut_texture = device.create_texture_from_png(resources, "area-lut"); - let gamma_lut_texture = device.create_texture_from_png(resources, "gamma-lut"); + let area_lut_texture = + device.create_texture_from_png(resources, "area-lut", TextureFormat::RGBA8); + let gamma_lut_texture = + device.create_texture_from_png(resources, "gamma-lut", TextureFormat::R8); let quad_vertex_positions_buffer = device.create_buffer(BufferUploadMode::Static); device.allocate_buffer(&quad_vertex_positions_buffer, @@ -906,7 +908,10 @@ impl Renderer where D: Device { if let Some(alpha_tile_page) = self.back_frame.alpha_tile_pages.get(&tile_page) { uniforms.push((&self.tile_program.mask_texture_0_uniform, - UniformData::TextureUnit(textures.len() as u32))); + UniformData::TextureUnit(textures.len() as u32))); + uniforms.push((&self.tile_program.mask_texture_size_0_uniform, + UniformData::Vec2(F32x2::new(MASK_FRAMEBUFFER_WIDTH as f32, + MASK_FRAMEBUFFER_HEIGHT as f32)))); textures.push(self.device.framebuffer_texture(&alpha_tile_page.framebuffer)); } @@ -919,16 +924,16 @@ impl Renderer where D: Device { self.device.set_texture_sampling_mode(color_texture_page, color_texture.sampling_flags); uniforms.push((&self.tile_program.color_texture_0_uniform, - UniformData::TextureUnit(textures.len() as u32))); - uniforms.push((&self.tile_program.color_texture_0_size_uniform, - UniformData::Vec2(color_texture_size.0))); + UniformData::TextureUnit(textures.len() as u32))); + uniforms.push((&self.tile_program.color_texture_size_0_uniform, + UniformData::Vec2(color_texture_size.0))); textures.push(color_texture_page); ctrl |= color_texture.composite_op.to_combine_mode() << COMBINER_CTRL_COLOR_COMBINE_SHIFT; } None => { - uniforms.push((&self.tile_program.color_texture_0_size_uniform, + uniforms.push((&self.tile_program.color_texture_size_0_uniform, UniformData::Vec2(F32x2::default()))); } } @@ -1913,7 +1918,7 @@ struct AlphaTilePage where D: Device { impl AlphaTilePage where D: Device { fn new(device: &mut D) -> AlphaTilePage { let framebuffer_size = vec2i(MASK_FRAMEBUFFER_WIDTH, MASK_FRAMEBUFFER_HEIGHT); - let framebuffer_texture = device.create_texture(TextureFormat::R16F, framebuffer_size); + let framebuffer_texture = device.create_texture(TextureFormat::RGBA16F, framebuffer_size); let framebuffer = device.create_framebuffer(framebuffer_texture); AlphaTilePage { buffered_fills: vec![], diff --git a/renderer/src/gpu/shaders.rs b/renderer/src/gpu/shaders.rs index 56005758..4b34186b 100644 --- a/renderer/src/gpu/shaders.rs +++ b/renderer/src/gpu/shaders.rs @@ -388,7 +388,7 @@ pub struct FillComputeProgram where D: Device { impl FillComputeProgram where D: Device { pub fn new(device: &D, resources: &dyn ResourceLoader) -> FillComputeProgram { let mut program = device.create_compute_program(resources, "fill"); - let local_size = ComputeDimensions { x: TILE_WIDTH, y: TILE_HEIGHT, z: 1 }; + let local_size = ComputeDimensions { x: TILE_WIDTH, y: TILE_HEIGHT / 4, z: 1 }; device.set_compute_program_local_size(&mut program, local_size); let dest_uniform = device.get_uniform(&program, "Dest"); @@ -418,10 +418,11 @@ pub struct TileProgram where D: Device { pub texture_metadata_size_uniform: D::Uniform, pub dest_texture_uniform: D::Uniform, pub color_texture_0_uniform: D::Uniform, + pub color_texture_size_0_uniform: D::Uniform, pub color_texture_1_uniform: D::Uniform, pub mask_texture_0_uniform: D::Uniform, + pub mask_texture_size_0_uniform: D::Uniform, pub gamma_lut_uniform: D::Uniform, - pub color_texture_0_size_uniform: D::Uniform, pub filter_params_0_uniform: D::Uniform, pub filter_params_1_uniform: D::Uniform, pub filter_params_2_uniform: D::Uniform, @@ -438,10 +439,11 @@ impl TileProgram where D: Device { let texture_metadata_size_uniform = device.get_uniform(&program, "TextureMetadataSize"); let dest_texture_uniform = device.get_uniform(&program, "DestTexture"); let color_texture_0_uniform = device.get_uniform(&program, "ColorTexture0"); + let color_texture_size_0_uniform = device.get_uniform(&program, "ColorTextureSize0"); let color_texture_1_uniform = device.get_uniform(&program, "ColorTexture1"); let mask_texture_0_uniform = device.get_uniform(&program, "MaskTexture0"); + let mask_texture_size_0_uniform = device.get_uniform(&program, "MaskTextureSize0"); let gamma_lut_uniform = device.get_uniform(&program, "GammaLUT"); - let color_texture_0_size_uniform = device.get_uniform(&program, "ColorTexture0Size"); let filter_params_0_uniform = device.get_uniform(&program, "FilterParams0"); let filter_params_1_uniform = device.get_uniform(&program, "FilterParams1"); let filter_params_2_uniform = device.get_uniform(&program, "FilterParams2"); @@ -455,10 +457,11 @@ impl TileProgram where D: Device { texture_metadata_size_uniform, dest_texture_uniform, color_texture_0_uniform, + color_texture_size_0_uniform, color_texture_1_uniform, mask_texture_0_uniform, + mask_texture_size_0_uniform, gamma_lut_uniform, - color_texture_0_size_uniform, filter_params_0_uniform, filter_params_1_uniform, filter_params_2_uniform, diff --git a/resources/shaders/gl3/fill.fs.glsl b/resources/shaders/gl3/fill.fs.glsl index 09a8069b..7541b242 100644 --- a/resources/shaders/gl3/fill.fs.glsl +++ b/resources/shaders/gl3/fill.fs.glsl @@ -28,7 +28,7 @@ precision highp sampler2D; -float computeCoverage(vec2 from, vec2 to, sampler2D areaLUT){ +vec4 computeCoverage(vec2 from, vec2 to, sampler2D areaLUT){ vec2 left = from . x < to . x ? from : to, right = from . x < to . x ? to : from; @@ -43,7 +43,7 @@ float computeCoverage(vec2 from, vec2 to, sampler2D areaLUT){ float dX = window . x - window . y; - return texture(areaLUT, vec2(y + 8.0, abs(d * dX))/ 16.0). r * dX; + return texture(areaLUT, vec2(y + 8.0, abs(d * dX))/ 16.0)* dX; } @@ -55,6 +55,6 @@ in vec2 vTo; out vec4 oFragColor; void main(){ - oFragColor = vec4(computeCoverage(vFrom, vTo, uAreaLUT)); + oFragColor = computeCoverage(vFrom, vTo, uAreaLUT); } diff --git a/resources/shaders/gl3/fill.vs.glsl b/resources/shaders/gl3/fill.vs.glsl index 06970424..6f530e42 100644 --- a/resources/shaders/gl3/fill.vs.glsl +++ b/resources/shaders/gl3/fill.vs.glsl @@ -31,7 +31,7 @@ out vec2 vTo; vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth){ uint tilesPerRow = uint(stencilTextureWidth / uTileSize . x); uvec2 tileOffset = uvec2(tileIndex % tilesPerRow, tileIndex / tilesPerRow); - return vec2(tileOffset)* uTileSize; + return vec2(tileOffset)* uTileSize * vec2(1.0, 0.25); } void main(){ @@ -49,9 +49,15 @@ void main(){ position . y = floor(min(from . y, to . y)); else position . y = uTileSize . y; + position . y = floor(position . y * 0.25); - vFrom = from - position; - vTo = to - position; + + + + + vec2 offset = vec2(0.0, 1.5)- position * vec2(1.0, 4.0); + vFrom = from + offset; + vTo = to + offset; vec2 globalPosition =(tileOrigin + position)/ uFramebufferSize * 2.0 - 1.0; diff --git a/resources/shaders/gl3/tile.fs.glsl b/resources/shaders/gl3/tile.fs.glsl index 7b9f0ac4..9cd13cf7 100644 --- a/resources/shaders/gl3/tile.fs.glsl +++ b/resources/shaders/gl3/tile.fs.glsl @@ -84,11 +84,12 @@ uniform sampler2D uColorTexture0; uniform sampler2D uMaskTexture0; uniform sampler2D uDestTexture; uniform sampler2D uGammaLUT; +uniform vec2 uColorTextureSize0; +uniform vec2 uMaskTextureSize0; uniform vec4 uFilterParams0; uniform vec4 uFilterParams1; uniform vec4 uFilterParams2; uniform vec2 uFramebufferSize; -uniform vec2 uColorTexture0Size; uniform int uCtrl; in vec3 vMaskTexCoord0; @@ -544,11 +545,16 @@ vec4 composite(vec4 srcColor, float sampleMask(float maskAlpha, sampler2D maskTexture, + vec2 maskTextureSize, vec3 maskTexCoord, int maskCtrl){ if(maskCtrl == 0) return maskAlpha; - float coverage = texture(maskTexture, maskTexCoord . xy). r + maskTexCoord . z; + + ivec2 maskTexCoordI = ivec2(floor(maskTexCoord . xy)); + vec4 texel = texture(maskTexture,(vec2(maskTexCoordI / ivec2(1, 4))+ 0.5)/ maskTextureSize); + float coverage = texel[maskTexCoordI . y % 4]+ maskTexCoord . z; + if((maskCtrl & 0x1)!= 0) coverage = abs(coverage); else @@ -562,7 +568,7 @@ void calculateColor(int tileCtrl, int ctrl){ int maskCtrl0 =(tileCtrl >> 0)& 0x3; float maskAlpha = 1.0; - maskAlpha = sampleMask(maskAlpha, uMaskTexture0, vMaskTexCoord0, maskCtrl0); + maskAlpha = sampleMask(maskAlpha, uMaskTexture0, uMaskTextureSize0, vMaskTexCoord0, maskCtrl0); vec4 color = vBaseColor; @@ -573,7 +579,7 @@ void calculateColor(int tileCtrl, int ctrl){ vec4 color0 = filterColor(vColorTexCoord0, uColorTexture0, uGammaLUT, - uColorTexture0Size, + uColorTextureSize0, gl_FragCoord . xy, uFramebufferSize, uFilterParams0, diff --git a/resources/shaders/gl3/tile.vs.glsl b/resources/shaders/gl3/tile.vs.glsl index f385d7a3..029f364c 100644 --- a/resources/shaders/gl3/tile.vs.glsl +++ b/resources/shaders/gl3/tile.vs.glsl @@ -36,7 +36,7 @@ void main(){ vec2 tileOrigin = vec2(aTileOrigin), tileOffset = vec2(aTileOffset); vec2 position =(tileOrigin + tileOffset)* uTileSize; - vec2 maskTexCoord0 =(vec2(aMaskTexCoord0)+ tileOffset)/ 256.0; + vec2 maskTexCoord0 =(vec2(aMaskTexCoord0)+ tileOffset)* uTileSize; vec2 textureMetadataScale = vec2(1.0)/ vec2(uTextureMetadataSize); vec2 metadataEntryCoord = vec2(aColor % 128 * 4, aColor / 128); diff --git a/resources/shaders/gl3/tile_clip.fs.glsl b/resources/shaders/gl3/tile_clip.fs.glsl index 56ae412c..93ffe55e 100644 --- a/resources/shaders/gl3/tile_clip.fs.glsl +++ b/resources/shaders/gl3/tile_clip.fs.glsl @@ -23,7 +23,6 @@ in float vBackdrop; out vec4 oFragColor; void main(){ - float alpha = clamp(abs(texture(uSrc, vTexCoord). r + vBackdrop), 0.0, 1.0); - oFragColor = vec4(alpha, 0.0, 0.0, 1.0); + oFragColor = clamp(abs(texture(uSrc, vTexCoord)+ vBackdrop), 0.0, 1.0); } diff --git a/resources/shaders/metal/fill.cs.metal b/resources/shaders/metal/fill.cs.metal index e7b20b48..b1ad19ce 100644 --- a/resources/shaders/metal/fill.cs.metal +++ b/resources/shaders/metal/fill.cs.metal @@ -21,10 +21,10 @@ struct bNextFills int iNextFills[1]; }; -constant uint3 gl_WorkGroupSize [[maybe_unused]] = uint3(16u, 16u, 1u); +constant uint3 gl_WorkGroupSize [[maybe_unused]] = uint3(16u, 4u, 1u); static inline __attribute__((always_inline)) -float computeCoverage(thread const float2& from, thread const float2& to, thread const texture2d areaLUT, thread const sampler areaLUTSmplr) +float4 computeCoverage(thread const float2& from, thread const float2& to, thread const texture2d areaLUT, thread const sampler areaLUTSmplr) { float2 left = select(to, from, bool2(from.x < to.x)); float2 right = select(from, to, bool2(from.x < to.x)); @@ -34,34 +34,32 @@ float computeCoverage(thread const float2& from, thread const float2& to, thread float y = mix(left.y, right.y, t); float d = (right.y - left.y) / (right.x - left.x); float dX = window.x - window.y; - return areaLUT.sample(areaLUTSmplr, (float2(y + 8.0, abs(d * dX)) / float2(16.0)), level(0.0)).x * dX; + return areaLUT.sample(areaLUTSmplr, (float2(y + 8.0, abs(d * dX)) / float2(16.0)), level(0.0)) * dX; } -kernel void main0(constant int& uFirstTileIndex [[buffer(0)]], const device bFillTileMap& _165 [[buffer(1)]], const device bFills& _186 [[buffer(2)]], const device bNextFills& _269 [[buffer(3)]], texture2d uAreaLUT [[texture(0)]], texture2d uDest [[texture(1)]], sampler uAreaLUTSmplr [[sampler(0)]], uint3 gl_LocalInvocationID [[thread_position_in_threadgroup]], uint3 gl_WorkGroupID [[threadgroup_position_in_grid]]) +kernel void main0(constant int& uFirstTileIndex [[buffer(0)]], const device bFillTileMap& _150 [[buffer(1)]], const device bFills& _173 [[buffer(2)]], const device bNextFills& _256 [[buffer(3)]], texture2d uAreaLUT [[texture(0)]], texture2d uDest [[texture(1)]], sampler uAreaLUTSmplr [[sampler(0)]], uint3 gl_LocalInvocationID [[thread_position_in_threadgroup]], uint3 gl_WorkGroupID [[threadgroup_position_in_grid]]) { - int2 tileSubCoord = int2(gl_LocalInvocationID.xy); + int2 tileSubCoord = int2(gl_LocalInvocationID.xy) * int2(1, 4); uint tileIndexOffset = gl_WorkGroupID.z; uint tileIndex = tileIndexOffset + uint(uFirstTileIndex); - int2 tileOrigin = int2(int(tileIndex & 255u), int((tileIndex >> 8u) & 255u)) * int2(16); - int2 destCoord = tileOrigin + tileSubCoord; - int fillIndex = _165.iFillTileMap[tileIndex]; + int fillIndex = _150.iFillTileMap[tileIndex]; if (fillIndex < 0) { return; } - float coverage = 0.0; + float4 coverages = float4(0.0); do { - uint2 fill = _186.iFills[fillIndex]; + uint2 fill = _173.iFills[fillIndex]; float2 from = float2(float(fill.y & 15u), float((fill.y >> 4u) & 15u)) + (float2(float(fill.x & 255u), float((fill.x >> 8u) & 255u)) / float2(256.0)); float2 to = float2(float((fill.y >> 8u) & 15u), float((fill.y >> 12u) & 15u)) + (float2(float((fill.x >> 16u) & 255u), float((fill.x >> 24u) & 255u)) / float2(256.0)); - from -= (float2(tileSubCoord) + float2(0.5)); - to -= (float2(tileSubCoord) + float2(0.5)); - float2 param = from; - float2 param_1 = to; - coverage += computeCoverage(param, param_1, uAreaLUT, uAreaLUTSmplr); - fillIndex = _269.iNextFills[fillIndex]; + float2 param = from - (float2(tileSubCoord) + float2(0.5)); + float2 param_1 = to - (float2(tileSubCoord) + float2(0.5)); + coverages += computeCoverage(param, param_1, uAreaLUT, uAreaLUTSmplr); + fillIndex = _256.iNextFills[fillIndex]; } while (fillIndex >= 0); - uDest.write(float4(coverage), uint2(destCoord)); + int2 tileOrigin = int2(int(tileIndex & 255u), int((tileIndex >> 8u) & 255u)) * int2(16, 4); + int2 destCoord = tileOrigin + int2(gl_LocalInvocationID.xy); + uDest.write(coverages, uint2(destCoord)); } diff --git a/resources/shaders/metal/fill.fs.metal b/resources/shaders/metal/fill.fs.metal index 28fd8afa..77892f26 100644 --- a/resources/shaders/metal/fill.fs.metal +++ b/resources/shaders/metal/fill.fs.metal @@ -18,7 +18,7 @@ struct main0_in }; static inline __attribute__((always_inline)) -float computeCoverage(thread const float2& from, thread const float2& to, thread const texture2d areaLUT, thread const sampler areaLUTSmplr) +float4 computeCoverage(thread const float2& from, thread const float2& to, thread const texture2d areaLUT, thread const sampler areaLUTSmplr) { float2 left = select(to, from, bool2(from.x < to.x)); float2 right = select(from, to, bool2(from.x < to.x)); @@ -28,7 +28,7 @@ float computeCoverage(thread const float2& from, thread const float2& to, thread float y = mix(left.y, right.y, t); float d = (right.y - left.y) / (right.x - left.x); float dX = window.x - window.y; - return areaLUT.sample(areaLUTSmplr, (float2(y + 8.0, abs(d * dX)) / float2(16.0))).x * dX; + return areaLUT.sample(areaLUTSmplr, (float2(y + 8.0, abs(d * dX)) / float2(16.0))) * dX; } fragment main0_out main0(main0_in in [[stage_in]], texture2d uAreaLUT [[texture(0)]], sampler uAreaLUTSmplr [[sampler(0)]]) @@ -36,7 +36,7 @@ fragment main0_out main0(main0_in in [[stage_in]], texture2d uAreaLUT [[t main0_out out = {}; float2 param = in.vFrom; float2 param_1 = in.vTo; - out.oFragColor = float4(computeCoverage(param, param_1, uAreaLUT, uAreaLUTSmplr)); + out.oFragColor = computeCoverage(param, param_1, uAreaLUT, uAreaLUTSmplr); return out; } diff --git a/resources/shaders/metal/fill.vs.metal b/resources/shaders/metal/fill.vs.metal index 13c7b2d9..a8cf9a86 100644 --- a/resources/shaders/metal/fill.vs.metal +++ b/resources/shaders/metal/fill.vs.metal @@ -28,7 +28,7 @@ float2 computeTileOffset(thread const uint& tileIndex, thread const float& stenc { uint tilesPerRow = uint(stencilTextureWidth / uTileSize.x); uint2 tileOffset = uint2(tileIndex % tilesPerRow, tileIndex / tilesPerRow); - return float2(tileOffset) * uTileSize; + return (float2(tileOffset) * uTileSize) * float2(1.0, 0.25); } vertex main0_out main0(main0_in in [[stage_in]], constant float2& uTileSize [[buffer(0)]], constant float2& uFramebufferSize [[buffer(1)]]) @@ -56,8 +56,10 @@ vertex main0_out main0(main0_in in [[stage_in]], constant float2& uTileSize [[bu { position.y = uTileSize.y; } - out.vFrom = from - position; - out.vTo = to - position; + position.y = floor(position.y * 0.25); + float2 offset = float2(0.0, 1.5) - (position * float2(1.0, 4.0)); + out.vFrom = from + offset; + out.vTo = to + offset; float2 globalPosition = (((tileOrigin + position) / uFramebufferSize) * 2.0) - float2(1.0); globalPosition.y = -globalPosition.y; out.gl_Position = float4(globalPosition, 0.0, 1.0); diff --git a/resources/shaders/metal/tile.fs.metal b/resources/shaders/metal/tile.fs.metal index fcb15dd7..1b2c7e9d 100644 --- a/resources/shaders/metal/tile.fs.metal +++ b/resources/shaders/metal/tile.fs.metal @@ -6,7 +6,7 @@ using namespace metal; -constant float3 _1041 = {}; +constant float3 _1042 = {}; struct main0_out { @@ -29,13 +29,15 @@ inline Tx mod(Tx x, Ty y) } static inline __attribute__((always_inline)) -float sampleMask(thread const float& maskAlpha, thread const texture2d maskTexture, thread const sampler maskTextureSmplr, thread const float3& maskTexCoord, thread const int& maskCtrl) +float sampleMask(thread const float& maskAlpha, thread const texture2d maskTexture, thread const sampler maskTextureSmplr, thread const float2& maskTextureSize, thread const float3& maskTexCoord, thread const int& maskCtrl) { if (maskCtrl == 0) { return maskAlpha; } - float coverage = maskTexture.sample(maskTextureSmplr, maskTexCoord.xy).x + maskTexCoord.z; + int2 maskTexCoordI = int2(floor(maskTexCoord.xy)); + float4 texel = maskTexture.sample(maskTextureSmplr, ((float2(maskTexCoordI / int2(1, 4)) + float2(0.5)) / maskTextureSize)); + float coverage = texel[maskTexCoordI.y % 4] + maskTexCoord.z; if ((maskCtrl & 1) != 0) { coverage = abs(coverage); @@ -69,16 +71,16 @@ float4 filterRadialGradient(thread const float2& colorTexCoord, thread const tex { ts = ts.yx; } - float _554; + float _555; if (ts.x >= 0.0) { - _554 = ts.x; + _555 = ts.x; } else { - _554 = ts.y; + _555 = ts.y; } - float t = _554; + float t = _555; color = colorTexture.sample(colorTextureSmplr, (uvOrigin + float2(fast::clamp(t, 0.0, 1.0), 0.0))); } return color; @@ -92,19 +94,19 @@ float4 filterBlur(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const float2& colorTexCoord, thread const float4& kernel0, thread const float& onePixel) { bool wide = kernel0.x > 0.0; - float _235; + float _236; if (wide) { float param = (-4.0) * onePixel; float2 param_1 = colorTexCoord; - _235 = filterTextSample1Tap(param, colorTexture, colorTextureSmplr, param_1); + _236 = filterTextSample1Tap(param, colorTexture, colorTextureSmplr, param_1); } else { - _235 = 0.0; + _236 = 0.0; } float param_2 = (-3.0) * onePixel; float2 param_3 = colorTexCoord; @@ -136,7 +138,7 @@ void filterTextSample9Tap(thread float4& outAlphaLeft, thread float& outAlphaCen float2 param_5 = colorTexCoord; float param_6 = (-1.0) * onePixel; float2 param_7 = colorTexCoord; - outAlphaLeft = float4(_235, filterTextSample1Tap(param_2, colorTexture, colorTextureSmplr, param_3), filterTextSample1Tap(param_4, colorTexture, colorTextureSmplr, param_5), filterTextSample1Tap(param_6, colorTexture, colorTextureSmplr, param_7)); + outAlphaLeft = float4(_236, filterTextSample1Tap(param_2, colorTexture, colorTextureSmplr, param_3), filterTextSample1Tap(param_4, colorTexture, colorTextureSmplr, param_5), filterTextSample1Tap(param_6, colorTexture, colorTextureSmplr, param_7)); float param_8 = 0.0; float2 param_9 = colorTexCoord; outAlphaCenter = filterTextSample1Tap(param_8, colorTexture, colorTextureSmplr, param_9); @@ -146,18 +148,18 @@ void filterTextSample9Tap(thread float4& outAlphaLeft, thread float& outAlphaCen float2 param_13 = colorTexCoord; float param_14 = 3.0 * onePixel; float2 param_15 = colorTexCoord; - float _295; + float _296; if (wide) { float param_16 = 4.0 * onePixel; float2 param_17 = colorTexCoord; - _295 = filterTextSample1Tap(param_16, colorTexture, colorTextureSmplr, param_17); + _296 = filterTextSample1Tap(param_16, colorTexture, colorTextureSmplr, param_17); } else { - _295 = 0.0; + _296 = 0.0; } - outAlphaRight = float4(filterTextSample1Tap(param_10, colorTexture, colorTextureSmplr, param_11), filterTextSample1Tap(param_12, colorTexture, colorTextureSmplr, param_13), filterTextSample1Tap(param_14, colorTexture, colorTextureSmplr, param_15), _295); + outAlphaRight = float4(filterTextSample1Tap(param_10, colorTexture, colorTextureSmplr, param_11), filterTextSample1Tap(param_12, colorTexture, colorTextureSmplr, param_13), filterTextSample1Tap(param_14, colorTexture, colorTextureSmplr, param_15), _296); } static inline __attribute__((always_inline)) @@ -307,34 +309,34 @@ float3 compositeScreen(thread const float3& destColor, thread const float3& srcC static inline __attribute__((always_inline)) float3 compositeSelect(thread const bool3& cond, thread const float3& ifTrue, thread const float3& ifFalse) { - float _725; + float _726; if (cond.x) { - _725 = ifTrue.x; + _726 = ifTrue.x; } else { - _725 = ifFalse.x; + _726 = ifFalse.x; } - float _736; + float _737; if (cond.y) { - _736 = ifTrue.y; + _737 = ifTrue.y; } else { - _736 = ifFalse.y; + _737 = ifFalse.y; } - float _747; + float _748; if (cond.z) { - _747 = ifTrue.z; + _748 = ifTrue.z; } else { - _747 = ifFalse.z; + _748 = ifFalse.z; } - return float3(_725, _736, _747); + return float3(_726, _737, _748); } static inline __attribute__((always_inline)) @@ -379,16 +381,16 @@ float3 compositeSoftLight(thread const float3& destColor, thread const float3& s static inline __attribute__((always_inline)) float compositeDivide(thread const float& num, thread const float& denom) { - float _761; + float _762; if (denom != 0.0) { - _761 = num / denom; + _762 = num / denom; } else { - _761 = 0.0; + _762 = 0.0; } - return _761; + return _762; } static inline __attribute__((always_inline)) @@ -398,25 +400,25 @@ float3 compositeRGBToHSL(thread const float3& rgb) float xMin = fast::min(fast::min(rgb.x, rgb.y), rgb.z); float c = v - xMin; float l = mix(xMin, v, 0.5); - float3 _867; + float3 _868; if (rgb.x == v) { - _867 = float3(0.0, rgb.yz); + _868 = float3(0.0, rgb.yz); } else { - float3 _880; + float3 _881; if (rgb.y == v) { - _880 = float3(2.0, rgb.zx); + _881 = float3(2.0, rgb.zx); } else { - _880 = float3(4.0, rgb.xy); + _881 = float3(4.0, rgb.xy); } - _867 = _880; + _868 = _881; } - float3 terms = _867; + float3 terms = _868; float param = ((terms.x * c) + terms.y) - terms.z; float param_1 = c; float h = 1.0471975803375244140625 * compositeDivide(param, param_1); @@ -553,51 +555,52 @@ float4 composite(thread const float4& srcColor, thread const texture2d de } static inline __attribute__((always_inline)) -void calculateColor(thread const int& tileCtrl, thread const int& ctrl, thread texture2d uMaskTexture0, thread const sampler uMaskTexture0Smplr, thread float3& vMaskTexCoord0, thread float4& vBaseColor, thread float2& vColorTexCoord0, thread texture2d uColorTexture0, thread const sampler uColorTexture0Smplr, thread texture2d uGammaLUT, thread const sampler uGammaLUTSmplr, thread float2 uColorTexture0Size, thread float4& gl_FragCoord, thread float2 uFramebufferSize, thread float4 uFilterParams0, thread float4 uFilterParams1, thread float4 uFilterParams2, thread texture2d uDestTexture, thread const sampler uDestTextureSmplr, thread float4& oFragColor) +void calculateColor(thread const int& tileCtrl, thread const int& ctrl, thread texture2d uMaskTexture0, thread const sampler uMaskTexture0Smplr, thread float2 uMaskTextureSize0, thread float3& vMaskTexCoord0, thread float4& vBaseColor, thread float2& vColorTexCoord0, thread texture2d uColorTexture0, thread const sampler uColorTexture0Smplr, thread texture2d uGammaLUT, thread const sampler uGammaLUTSmplr, thread float2 uColorTextureSize0, thread float4& gl_FragCoord, thread float2 uFramebufferSize, thread float4 uFilterParams0, thread float4 uFilterParams1, thread float4 uFilterParams2, thread texture2d uDestTexture, thread const sampler uDestTextureSmplr, thread float4& oFragColor) { int maskCtrl0 = (tileCtrl >> 0) & 3; float maskAlpha = 1.0; float param = maskAlpha; - float3 param_1 = vMaskTexCoord0; - int param_2 = maskCtrl0; - maskAlpha = sampleMask(param, uMaskTexture0, uMaskTexture0Smplr, param_1, param_2); + float2 param_1 = uMaskTextureSize0; + float3 param_2 = vMaskTexCoord0; + int param_3 = maskCtrl0; + maskAlpha = sampleMask(param, uMaskTexture0, uMaskTexture0Smplr, param_1, param_2, param_3); float4 color = vBaseColor; int color0Combine = (ctrl >> 6) & 3; if (color0Combine != 0) { int color0Filter = (ctrl >> 4) & 3; - float2 param_3 = vColorTexCoord0; - float2 param_4 = uColorTexture0Size; - float2 param_5 = gl_FragCoord.xy; - float2 param_6 = uFramebufferSize; - float4 param_7 = uFilterParams0; - float4 param_8 = uFilterParams1; - float4 param_9 = uFilterParams2; - int param_10 = color0Filter; - float4 color0 = filterColor(param_3, uColorTexture0, uColorTexture0Smplr, uGammaLUT, uGammaLUTSmplr, param_4, param_5, param_6, param_7, param_8, param_9, param_10); - float4 param_11 = color; - float4 param_12 = color0; - int param_13 = color0Combine; - color = combineColor0(param_11, param_12, param_13); + float2 param_4 = vColorTexCoord0; + float2 param_5 = uColorTextureSize0; + float2 param_6 = gl_FragCoord.xy; + float2 param_7 = uFramebufferSize; + float4 param_8 = uFilterParams0; + float4 param_9 = uFilterParams1; + float4 param_10 = uFilterParams2; + int param_11 = color0Filter; + float4 color0 = filterColor(param_4, uColorTexture0, uColorTexture0Smplr, uGammaLUT, uGammaLUTSmplr, param_5, param_6, param_7, param_8, param_9, param_10, param_11); + float4 param_12 = color; + float4 param_13 = color0; + int param_14 = color0Combine; + color = combineColor0(param_12, param_13, param_14); } color.w *= maskAlpha; int compositeOp = (ctrl >> 8) & 15; - float4 param_14 = color; - float2 param_15 = uFramebufferSize; - float2 param_16 = gl_FragCoord.xy; - int param_17 = compositeOp; - color = composite(param_14, uDestTexture, uDestTextureSmplr, param_15, param_16, param_17); - float3 _1325 = color.xyz * color.w; - color = float4(_1325.x, _1325.y, _1325.z, color.w); + float4 param_15 = color; + float2 param_16 = uFramebufferSize; + float2 param_17 = gl_FragCoord.xy; + int param_18 = compositeOp; + color = composite(param_15, uDestTexture, uDestTextureSmplr, param_16, param_17, param_18); + float3 _1347 = color.xyz * color.w; + color = float4(_1347.x, _1347.y, _1347.z, color.w); oFragColor = color; } -fragment main0_out main0(main0_in in [[stage_in]], constant int& uCtrl [[buffer(5)]], constant float2& uColorTexture0Size [[buffer(0)]], constant float2& uFramebufferSize [[buffer(1)]], constant float4& uFilterParams0 [[buffer(2)]], constant float4& uFilterParams1 [[buffer(3)]], constant float4& uFilterParams2 [[buffer(4)]], texture2d uMaskTexture0 [[texture(0)]], texture2d uColorTexture0 [[texture(1)]], texture2d uGammaLUT [[texture(2)]], texture2d uDestTexture [[texture(3)]], sampler uMaskTexture0Smplr [[sampler(0)]], sampler uColorTexture0Smplr [[sampler(1)]], sampler uGammaLUTSmplr [[sampler(2)]], sampler uDestTextureSmplr [[sampler(3)]], float4 gl_FragCoord [[position]]) +fragment main0_out main0(main0_in in [[stage_in]], constant int& uCtrl [[buffer(6)]], constant float2& uMaskTextureSize0 [[buffer(0)]], constant float2& uColorTextureSize0 [[buffer(1)]], constant float2& uFramebufferSize [[buffer(2)]], constant float4& uFilterParams0 [[buffer(3)]], constant float4& uFilterParams1 [[buffer(4)]], constant float4& uFilterParams2 [[buffer(5)]], texture2d uMaskTexture0 [[texture(0)]], texture2d uColorTexture0 [[texture(1)]], texture2d uGammaLUT [[texture(2)]], texture2d uDestTexture [[texture(3)]], sampler uMaskTexture0Smplr [[sampler(0)]], sampler uColorTexture0Smplr [[sampler(1)]], sampler uGammaLUTSmplr [[sampler(2)]], sampler uDestTextureSmplr [[sampler(3)]], float4 gl_FragCoord [[position]]) { main0_out out = {}; int param = int(in.vTileCtrl); int param_1 = uCtrl; - calculateColor(param, param_1, uMaskTexture0, uMaskTexture0Smplr, in.vMaskTexCoord0, in.vBaseColor, in.vColorTexCoord0, uColorTexture0, uColorTexture0Smplr, uGammaLUT, uGammaLUTSmplr, uColorTexture0Size, gl_FragCoord, uFramebufferSize, uFilterParams0, uFilterParams1, uFilterParams2, uDestTexture, uDestTextureSmplr, out.oFragColor); + calculateColor(param, param_1, uMaskTexture0, uMaskTexture0Smplr, uMaskTextureSize0, in.vMaskTexCoord0, in.vBaseColor, in.vColorTexCoord0, uColorTexture0, uColorTexture0Smplr, uGammaLUT, uGammaLUTSmplr, uColorTextureSize0, gl_FragCoord, uFramebufferSize, uFilterParams0, uFilterParams1, uFilterParams2, uDestTexture, uDestTextureSmplr, out.oFragColor); return out; } diff --git a/resources/shaders/metal/tile.vs.metal b/resources/shaders/metal/tile.vs.metal index f05bf6ae..6b297b65 100644 --- a/resources/shaders/metal/tile.vs.metal +++ b/resources/shaders/metal/tile.vs.metal @@ -29,7 +29,7 @@ vertex main0_out main0(main0_in in [[stage_in]], constant int2& uTextureMetadata float2 tileOrigin = float2(in.aTileOrigin); float2 tileOffset = float2(in.aTileOffset); float2 position = (tileOrigin + tileOffset) * uTileSize; - float2 maskTexCoord0 = (float2(in.aMaskTexCoord0) + tileOffset) / float2(256.0); + float2 maskTexCoord0 = (float2(in.aMaskTexCoord0) + tileOffset) * uTileSize; float2 textureMetadataScale = float2(1.0) / float2(uTextureMetadataSize); float2 metadataEntryCoord = float2(float((in.aColor % 128) * 4), float(in.aColor / 128)); float2 colorTexMatrix0Coord = (metadataEntryCoord + float2(0.5)) * textureMetadataScale; diff --git a/resources/shaders/metal/tile_clip.fs.metal b/resources/shaders/metal/tile_clip.fs.metal index f35707f0..de4bf10a 100644 --- a/resources/shaders/metal/tile_clip.fs.metal +++ b/resources/shaders/metal/tile_clip.fs.metal @@ -18,8 +18,7 @@ struct main0_in fragment main0_out main0(main0_in in [[stage_in]], texture2d uSrc [[texture(0)]], sampler uSrcSmplr [[sampler(0)]]) { main0_out out = {}; - float alpha = fast::clamp(abs(uSrc.sample(uSrcSmplr, in.vTexCoord).x + in.vBackdrop), 0.0, 1.0); - out.oFragColor = float4(alpha, 0.0, 0.0, 1.0); + out.oFragColor = fast::clamp(abs(uSrc.sample(uSrcSmplr, in.vTexCoord) + float4(in.vBackdrop)), float4(0.0), float4(1.0)); return out; } diff --git a/resources/textures/area-lut.png b/resources/textures/area-lut.png index a576cc57d04a16189d5a3399271a74b13debd6c8..19b31cbfcd5881015cc9a2918b95c517f2d73269 100644 GIT binary patch literal 35027 zcmY&;2UJr*({_kN6vU_?QCh%46Nm+f2mz!@4OOHD6r_U&rGyfYF4CI_1SCY7f&vOi z2`bW*7C@x8P(lxc9>Ra(`@ZM=-?_;qXYW0;GtbUEGrN0Z3=On6*?HLk005`N zCnhVU{qJWSH3N@YfW(hYg-%?#1GDgt0P?u|F}H@9mrNn2j#dvZG8AZQb_&xv3hE7b zY~Yas2mLn?d355a1rjov^gmy{jee0q*d-bv9aBS$HXYCwmqT%Q}kkRec7tH8>0(~$!<-0DMG0_A}tlb;_XNEh68}ZRP z1GvjFbu>I%u7Io;ojAR&9KLb_D>W>vrl2uKIihXDB-hvejT`J38g(ST%4Y+(2}flG zRC2yg^RQp>J;j`s3xTh3Pmo`aer1SGoc_o~2R~GJwDx*T7rQeY%owab+8n8xpm#s{ zGC}cOUu9zMV9+SNLtd)X{1PTxiJ-sojb@^M99fN>$B_PGyutdKJyQ{%m!l1IQJyyF zgyecpu;e6k3c8c!10#llguFMN*NUQYWh(^UXRb^+)vMJL0l`AMLWm*UlcVvi=75bA z0Hs`=Qm&<-F)ZcU2HYF_1!N5F6dz8)M(HebzE3lh=@4MEwog$g2{Jp^<_%erz>mKI!$y!kqs^b2a zh|g-l_rI*b;+U`lk(D$DU1S}NtgCQ$ztm`0oK*HHz#TzZvvpb0wn7dElO~9y?UV@; zP`@nVH)k!};X@)*z-~qh)B8feJt@l*pGhch+%rmhhR+MyVE&kW;i-Aw@*}Uc?V~;Y zVGQX{3gh%PHgfYwAGb0Oou2p(G)%MpHR#~k)*$Q_@jk~Rnf}{-0>!fi3xNV~4=FL* z=2@bnDbk2Nl0l+Rq`>qpY=P3`bnA2JtRN%!WlgY*Onw0{oNpPO^@_G+^fc8j9Fch7QwFy8oKSpw(RX{QfA zi_E7UV0Sjix(fDRzn@O>iS^hizuwVF`cnxyBsPs;ZmzTfzvL0Ij>8Fj%8YgnmB7Y$ ziOiJpUx>Z^WLe1GMPX%cDlBFP(&T#znoTnwN-R5xn0g(EZ*jKcM| z+Q>4|2VKS6sk+E|QPl8BpYSSOMqHK}&%GsWo{u08g#TbL>CZt#mU8$QQF5IEwhi$b z`^^DJ`~SRCW8+qZP2f_ZO`A^#k$W5km=F(i##vHMZma7eKZ{yprkP&x@v_66aFBCD zE<=EX8Sv@hsoEk}ZdYyvJBFexCXaChI)oLP1s%>f&P96PP>QGRq~!B+6TFHakEJUMH&Z+qsk%Nm1y zWh_B*JtoW1Go3gC)X18EgmxaC-2w?Mxv8$U4`#ei^GKqvc?en; z_|7)xl7_RRh%>GD?-)WW?3S=N*ilPom2L1r;no%S0X8JSS-Bm!y$Tz3UkkPi1xv0c zft1jjixU(6q4}Lhp>JS76ad`-D2f{8tGLeuOSSR&7i0yWaNXwMw*!tzg)p!##+V># z-sQ6W49sLx7two`YlXzS%tqVu-C^)PA4ks%m%5CRHLecSn&dhM$9sk&OTaD{Ja|6` z5>nK8#PQV@;LQ90otY8pXcZYa;?6gr8H7hQFK_l8peUgYVoDVbj zRoj47W0yYc^i9;rljjma?g*D9?GfZ~-S!8%Sw$0@C*uO?M~`4vCn<{n|21L9(&)Hp zu3dR>u8kv)qMO$!O&(ADtoo-*PP_4Wo3w*M)G`TmYz-E&c0#FWD*LZ|18(l^|9By9 zD|lbQbgv-`(llWqgyW0CIb_DU=a5bfgYi zvv-*W*)5!KVQCa_ojD@yUvr&_+D+)0+qt^_4g#1^8oBM@ThX}@#Z2T1pyK=m2@_^?$L{*Owcka!2H8FA*XjNd~b^Sj! z^u^kTq8>Cg9-c3OTG*{kAc?)eE$xENqg%C|&}k34|H;5B6DUA#n;YMoz`8`Qx;E+e zD-OP@K5o>gpGg3l9>zx|9nQRtnsMRB=tOzH7?AJ9ZD{Zqy41=L$-Z8;0AYAfu#siCE$is@ z-!QNK@k2JaF4n&gJ5@gF48^LfOzdA}BxpeHfw4b$T5scWApjc4&T})$FAM9u zIe+zIidXHISTCp}oM(uj@lBfX`rn}IQ}>Y5JzNrqT2h|^%{1UUq*k8XqTzYEHuF#E z9JC@t%BKes@_sz6ua5XJq`?jRi(-G^b8l@QxLX2J^;Epw2_;Q7vO3rTdRm~v^olR| z_Uhk0oI??bHYbM&8a^<(Ff%M#rha6a+!i&->AO?tYf!a#GJ`sL?^haHt-eElv3r!e z+hMpoHS5Rj;#e*on%9UiKTHGBwmr6Scv8V4Eq7!X!TdI2Z z7M$ei&q9BXo=N1f?mW6x^>ey5i6K=Ha`=Yf7@Aw#+nBcfvEkqvJjjkEKrZMvkmkAh zizm-FYEM1zW?Qx&A8P;H$Gus$di^zy$;;z&&_7>P%r-u#`Pe6oS&Y1|2#8hYXJ(=6 zf!Do(1M*4UI`iZb5|`9`cwhCvE`F4IR>bnU{?RSlv@Zj$l?yTqu|vMu3^)hEQ9L?S@b%_>h~?~icZ8AtQ!A%b7p znAQG|sYB3u-1n%PG@3S&++!kv_^gJcCE^Y#^CCp=Qfh6|7Y>(iGRnk&(mi|2(sr0f ze~XsIq!Z6N%1T)!TU3n3^-we!s5P~9)E_7~efflUZ}c3+E^Pvxhv|*1t^slz zD}!>rvkiI41eR!43=Ue3(lF(Psl48@7nag|rNiB^7{yj_P)i&#c!nA@ ze4bOInwTJjfz`TPOb`)2H)>df;11QneRO4PL4uJja8zX?I4_|$7kj8a4j-2eF}I(WRO!y| ze=sS^v!2o=D*c&i5zE(6x_RmK+>SKYp@<-zdQZDCpp8fAJ+-umPzN`~x109eM+hCS^voK#nT!cb&FN@b0-E8~;O3zv3e!BoG=1<{)W(J|1Rxdc@{ zY(M0i4Dr23UE+V}OZRGB-R{^7lT7Rczh_@J>uS&HE9#Ihb~U&G|9MMZFmC>xpGYg7 zs`sa~ircks$%XFsMrk~9u#ZMEijPNAv5hHf1%3e5zG5;fWkTd#^yV&4o%1v$4T5{Hf2fHe0u*pt*? zN0tLq>>g;@gP0+3q3#f}G&%Q~qxFU_Nt=PuRN@BUMZ(@wA{Tk`oof{DVB zG(2CT)#}+P6Rvb&xHS4lB5@Q?lk6`|1TBndFg67Q?w3bOo|zTd{{sot+4O<|+JRzN zhL-m+K|&Hfkq=@4D3Mcgz;);wc}6?26shuIooB$zdgU*@WaOPWWGF|&$0KRX+93n4 zdNekD2wLZCV!KW=U7?ZN<>N{UO{KfzVn?4{f=B4tcIZ!S$Tbl6lzZjmf|5{VvK)i0 z|J{y;A#ra7xi7CXL5fTS1>3W@$|)V445ujW;S{E!YWmG z99bAIwAw8uMS1d^vdE)tuqV-I@08>7YM%ggphIK`n9R^vKl_=Ywt*xwKzV=UJgZtf zrP7w?VwSh3ePeeZO}Vdz0tZZ<6GXiX-rk|5Mvm5`@ytyD}HRm50j1C~PvmykcbEKNaa9}|fy_?dt2x&y5TnbtW zH)5PC0#&?r2sIAA0MF5m4#JA*v3{7AKzMWpmRVuAdgmhgV752i|< z=>~p$g9G&ha;18yb^!@$SxTKSs$63g*yO@^^nFx@WBtd22P@DfeXUo|3asd2Up)7@3!&hd4l&JTt3_*)|l(55)VW1{TMy zaWu6ImTm$sJXgST8^%1*s>7TSVn-(vNOVa<*S-W&if$=%#biIlXC?#A(LQZ&~ zej&VAQ)B8`4ZX5Krb_+vL}Skf9h|xrLLG55sm_TG;F(#={z&L&s+LFK^WcMo#{si` zK3?K=U!S$D=I+`st36RWIqNiBeh%(q4zAJO;41uQX`-$E<}W|b=?q`fcjxU=LC$sC zGL5(_XKhz!MdF@?5c`TURvd|a=k;q)Hw0v8d9DJ0lOqh`2k6=l0G;7M6&y8*%F#+) z;HJLaURMk=y@ZtNm9AsKd6OuB-uGMLTwBrri&JQm!!t$m zkz~m;u-ce$jjOn<*uZ-{uf1ZXJ^VIZf?fIw_pXk=1CKamQH^}OD2X;oNrg|QeVJ7f z^wp5N83t1jY>TO?YM6}ya1wModph-(s=yT=T>njwXGauKe` zNWRd(Ml`uyfifl*WW||4BF@&@i*uM-{axU*2uc6Zx2qRl0g#F<+({>;NU9u>9xLo! z!-3YT9%A?jM2nExhYa=y8X~{dy{tLht(9Lv_4=Xrkh57=u@OOZQ3pA8DJ4S{1J~*1sh#R@ z3C)jFVz*G4&5)Du*ygaX+do6H)U`jFb7GlWaQStu_Is0JBA)@rN4wj~DiK>ZCnMR= z-fckIM@#ZMf(GwUB#{IOshZl!2pLiQ+UBX0Xan|V*Ckvye8*fN9-GocQHn2O@_o9B zP1HOmmB;Y0qcv@0u38rt=RURPy+nX}u=bB!=cBmDs)2pCpf)Jk&~YO%`2J5iC=p18 zg*tnRDbYcb*yCijNIl!HDOp(wSeB0=6E)7239D6mQF8Q{`ZKO0!gV#zMC%&c`ury0 z2~3|j0ml>;J}wk>Efif<_q>yoWk7z%RZG$13!c{+Zfb!JFDsO;$&oBYg9}nzzG)0^ zG;>KV(;|adAfptpBJ**PU6vA=!-&ym@AQd%Efbk76AwljlWY)4Hp)qb=;{9q!wR=} zDKEbG#2`gtzN%Ol9*z-dDOD4}I&)^$$W%=c#vc!^!2rBCQ2tv4oR?%Qyz@SoaG2NO zpy?*2nPGI8Vkff7@zIXw_Tfk2l#B|%;Y(GX5LelTj%L4NaIF)C&8H=Duv9I}_IcKp zF%;cx-0lez*f7mkg%MqtZYEUIu7J3zFn^`kyQ5#jb008}*sg#eNvtG3YUIZJb`f)K&hNdNMuu!vGm_wGu}W zY21jSHrHz)st-{h2a|p(- zg1woEuj`4oZ&$nFY&O4H7sax}LAzU%GO$rZTaZp0y^!#xuiCFof7WH&kDCRJOZd>9jY^J9MPXfPBEYiFm{uxvrQ|60F>C5o;KNBts|OlJWF4ti!hl&;09l5hy3})o|Ma5 z-X-Zb{lyEAze=xnuwLwr6Lgf76@jwX7T3G>mCDq~*{OQVje zmkV|@^E=&M?2VrnVOYWqnIs@qCOV$(>6xVI|0f z-i6?At8_%4Ytp7z@zAe-4Zh$dprj%WO$5K&V5y}RjNa=e?S5b`eytycilvO5H=XwV zO!e6BbzyuALhuyXw)fpmD%@)FI=u!9nK)TlT$0W8c9lk6&hFD73bIoM)uxO^{KGRU zRKz$9ZFA8v)cDT>re%Ez2Ac_GtyL+7PcI?ein;L<%p+eS z*J%GW1gFA$e-T>QjOHP$Xx-tkFDD_-i25%7r@b~bnDAw(cejeydqJRTKL!HeNPW}i zHG0oWYL-{(CyLTblQ=Rwm&$!i&>_|DJmUk%l;=5B?63p89rkKt>h+JPO|^9NWkjsk ztmgigR6fdtP35gK1RmLHiq?;9IeI-nbJLH!>d&t0yU{PWtIsT*6zt?o1u(p2*(&F-I61FgPn2YI0r=O^mXQ$+KX&aUQj(M10 zfW;@{;z;Gs-Fu&hPZhPkmiwm0b6YX442^G4jh!mRzDvlL3@OwvS(8Hs{Oq$-=Me(l zxf!_*I`$q!XE}`xQT_{^1phe*Yh8nKN66H70R7S;TPpz)rPS6+fWlR3-7pO^@Q&CB z$p0wV$LTMj>u;%BPJhhYEQ!03ReXA$y9jL%jk${8Bbfiv6=}uaY=vyKwRn*;d{TW} zU@i>5EL&uE-nQOe7~N4nt9diZyPoXps`%l2LUUgv4R&*KSm#qYrqV))1FfGH98%O= zy7{eNq>;#)LX}aH$b29{mEf8g2+JAS#h4A#jAG1#6fs+&ifM%8!Y|LnG>@J!x$#j= zrg_&%!`bXJ&z%g;b4AhIQ*EUZBK77Vm}mDm$O|MvzbpZCO2-;$TdZKBZAHdPNW2dwv^< z66iW<6eX{(*I!C=OAWTs>DEN#yT-34f}GuLJxo=^$)Vtv8ru_wK;fRLa2=#f%$$ ztl|Cex~nXLLp?M6aqDy}S+34@%Q0_w8NUf!zoF5-6|0y=P{WOr-d;E~Ve%#Cl%~!n z<)?=R$Y_3|!Pyiw#Rf~;MSIuelfp_`uR+PTYVYN=&k}HRNd5b{3wxhm`G9ZKYUeaw zgKaS=(1>G}*NANwov!cbe=f#BB24i;V4)8=N)AcHL62MKxjxOBGSGxmG#ATQf4`fhNmHT3*a7GUzxiOQJ8ow}q9rlDz&I^J(nAS!gSd?Y?hi^ANmvOYm(>sY9`azph$p%Np!3!Ks-wgu5B6(P z#eW}YpCZ;D-u)Qt92Byzf@z1iaBe<);J~tf|Br4+455E38D#0@j33!m%R=bbz=Evt zV6u2ln~C)uZakKk$xCNP)y4b$hZoauiEKCJoBQfqw#kcWdedTB;NgXH-N33--a1h2 z+>a{UBy)+CGt-;%$}1_^e}*s>CyGuDe~JDgmG2SjS0%*=c|0yhsy0cL4^-L67u@E^ zBXV;=+kk}M_UHVKu!+abJ+`lTY>RmZ>dhG+so+S*{NLzCNF13mUHjPY<0T3~!@jie z>^3qXkr78K-Er8+l)JN|Af%J zfkPFVm$=D6nD3^S!-iC*-jQnB!RuEQ-sZy#zEx8}Eb4C)JZS1Tl9Ydm=*wPZ5OWd$ zo_XAhiWHl9)8(&i1s@Suf)-^!t#&srR<6#SnfL@qp6jkb9f0kf7kyd zSi*F#kjc1duRBeAyG-uSX0w_}cNY*8GC!K$bSbtTKMoypNlDN-us2Q54bE5I9owBs z(J4Fni%Zu&I_VbnfU#381fe;HPr8LC$r}~fv$i*gJ!m?bYcjmN4NSF&Ro2fB&P$*M zUst~3RY$M=9vMj1-Mus!+e?-VF~&c&m=yI?_1I0IjyA)*edFEycqWUBl0}>v2=9OD zf3P0ZY7Yv;Z27%^QZM2EM%VXcuQLA{ETp{I9Van=a!q3XN|ESI7jarmv0y79Sz8xb z)von#T8av^(sB0`Qh6w^#d3U#xbaTerSr?j6S#2Zrt;7$FO3|r>#eYsY(lbj6ltNv zfkfiUtTY`a>-wK7p;6;-ZP|q6!o57>SBxp*5^sux>H(3=xngS z&8L)nPTP((jWV^gn5fe$QN0@RN{4wBfWfyqJ`0Agj$ezB|8A8SbMlgi{q>kT+2Dfx zKa1cMNIRPr7#&1O)Yi2C%$Bn#a%D)PgD5?kP#Y{r1v;~EX)xoof5|^+RDZuhkcekDUtEJ(Orm+C<>>)V!!YW9P=UeY7>o@|jvmFaCF*u86cFOEfx& zV!(&!P;8*xh-d0#fLmoUl^T<&RDZuKHs60%PP@`Jn^{w(eWdBl3Zu38^KTi62x4|1 zO?SH)vWcinQG8GmXaThFOU0u?CAA%QNTnTb6_`D~ zxJx_B(+w=rpAV4``g2AKX!WRvr}#$GP1f%h>@H4AFrKv}Rw>;U87AwRH4w>(bA|RF z1m3@^E<0_JmwE>-=y1$r#076<9;MVJ_WUq^FHLclpmt+FiWxef*PJHhwBqsJlgI*|5n>)&3&v@w`Y;(mVC~)A|&7I z{vV#>)v`6HGdH}-zOAUDUkdAnyn&ru(v9kW04N!t>1mcyufhvjfQAus z!k?Y{9up6*g}nLd9IS=gKK63~`eFyt1hJFT6X@a^dyd`T7jEDspdbia*LcA<+1l+_ z#~&4vN{#k;JkV;hxPa99E}{hGB+vSCiub6Ir;7ZBgn+TUrWwHOl$y{fJ5E#6KWFZ7 zhF)hiRuhtC{SIjTdMecLGVu4l9nIrZEt!KlOY1jmd@{TDT~7)b*G`t;`{Vz~Cliz} z2{7`$i0ssaC-^AQJ}CvUyqc#dEf~IH2s0x|v-zjm_Wl9&~*L?vb!F-(N6Y`zqqMIh^>Y&eDWH&y;c~< z7oU@$ns$&GqMBtxytg_tP`dPKO~SuJx11YKR2WAAa$|-hpU*4mKfjF(G~d6a)14kg z)$1u0ydkSq_Iy^)m@=__28n!TfcNI|2%8_B8MwTrH1%$q-&ZjdJ9ip!_Z9!#ohEbZ zYxw13@7*qhu7lUsf8c}d6m=p|s8fhW8F*UqQci1uF`6etMhO=~mEf|hwk{tg8=-@u zcc_a4TYr58HmWjK|EEsqkl0p-fAZoJ+^(_6f*D3mVkYxUxg2v^ZTpYIW_PSj(|6u4 z4dgr7bPgeNlzK}?!{^&z30K*iPbBW9MWf+oA6^C?{N_UTU0Xi;P&dz(^BW^{OZZmL ze!jKC=Jerrs%x~h;U2DO>gd&`Mj*)>1XxHtE{Wg*E1j zwN7?&I%D!kqcM4;bsEhjRY?kr*L!K6Mb(Qpznb!^Z=FiO?QP(E5xi>`wQ7qIor2xe z`1u-a^UOM{WIXobrFW9(`O8|)+W#)$Gb}d*&tEux-0s2!$;fZSjbN6lM%Hmy$UH~- zPVD00G*vHt9_Q!jnkIt{@O(KhQiEWTlT~USS;qfQIR*bkCRI+HUxS6fAIiB59=21; zvqj5DxwG)jUSon_$xR%emi*j`$FoI=$O&vwe?V94EMlIJIN?R*e_CEj19Rk^(>N`8j=g4@euRFv2_N}@ABi6{+!MJ{%i9wr8H;%^ChH5RaNri zmkDzVH?&dgy&JeOV+>2ev@YTZld5eq^PumRWL{xQ6TY9u9=(xpH~MC`N-(5hERDr1} za_JOPEb>x2rPBPe0%hFSX5w||q-*~B|KQ?e!euAtJa`yGS-XwQW=ImRLEb0qoLG@S zJS526#!a{w#mmXQ$;1pO{+K)S!A*>#3SC9j+a5KU@f8&K1{|H2y_uJO*Zv|f49FvN zL6XHV#`C!Dnbe1gVvc%NA(DhwE*xi0r8kL1Sbx8*k_t_(>T)p=fyk3Y;}jc0(3viuWw^p(e3>$(5j2w4=62Tl1s9hhL2L~uK~dkrK}k=8Y& zzhaQ3ZG@=fi`F(pT#Qj0)s4_g<#$Vs)G~=hM`ot-BOz6)JVkDDcMwf+Vp;-(7XCDf zX?u)VXy_$?%hfX(=hBQWsR~J7mB6bL(p0Qc z-9Q{t{3%q7_vep(K4G!K2DdF!nNzge=dr0$TzFWZg zB&@)2R`HX~tfED62F-<6QTF`Y37hj>H+196M>Pw-6&Le+m-)w+TRPSDXK&ibx5@|E zcO{89z0m28zgd(n(tkmrqvjL-)u-;%r;oHQKGM4Q>Ex-jyEEorN>PcOFZVTus)knE8qQu{V=(i+>!p zx7@N9R}@c4|1Oq-{}_LflbEWhTs&WsTG`!@2>N~a^FER0ys9SOMfg2m#ASr`^xjRl z8$W#=V1bck&F?^*V%!kRtBD5;JV|vQjxTwSnv!i&cEyWTs=CLd`!H}x#NTXDGHHnuaC{};_{zrh$u6k7Ll^Mtu}0_KhG&aKVOJa^ zVvJlO5Ag#+a=yj|56A?S)&>j2_6iqH-X$2$t}#<6$Llzw^6bKb)of8dN7Z_EWJiQi z=4Ft)XBLU>7k|SQua>H$Dc_3k@wV!K)!Z+88b}?DM!zz0wKmb#5)PFQL1p08!>^s< zem||T*D7=oIA$mFB6^wmpxc6oN^wQ7NelMvRxx2>?n$A*u>TyLdUsE7~N zJQYtdYLiWi@k@2nyOW9$uj zxVX7gFaC}9ry9D3$97S3sZ#KOVyo(EuxQ~!ieb?PocY@0$;T%B*XB@Z;Oue;_)5ujOpk8Ow7n z3EXm+uCUaUerprAC=%ZPvgQddb zkMiCuB|Ji_8MKn#8qRt#Q!D3VwSI;DXXM>OmlkhfTMwKf1R+uURxqPDt$Jun?yKO> z(uyuvK}&6mcVuzg{2eVgZicVB_h)JHCBdG{uP>%*=vg@I-8`jY{`sk7f(zddfS$+9 z13?oWi^kulZk}cSo;uc_aZJ7OdJ;^B6PW~y>5gnJk9)Yfp{L^L@2O689jv*~txuJF z;>Ty5hRF&lZVr&7J=c3PBhP76OvGO})L&-+YreThfAz-wE<@w;Ud=zOAAVJMVZv3g zTi?m+5q{KEFm9(l-}?O>-U+Aub^boTcn`(a+k!FkP@QPE|0I2FXL(iK|6ae&TAusw z6`z;gcwrp&zPijejOLj*&UwdQy2YDIet7&VE|xmV-Ca;wUAtxE+_$7w->QmgEvOAJ z_!-75lunML0Fd90i+r;S^H1-H-b3Aw*V$(lO5EjgdkC&H{$76Jc+Y?$)!>A;*}Ze>W?BhOz|V>KqykI&lc$I_3^0L6T@jfKR%Pqg@l zv{EVqSYs!r)_}&n{JKeF%}nG^qq)mCsG?2LsriCgvzZU%C2H;cMV`FE3&Wq{sM%Aq z@)P6%j%4qId0xSffX2`O{udJkr%tK-0Xz>CK4W}IsEnoP;RT@ZRp2!5B}h6E`a=8P z-BwT{YNoL;yLW3HB^Gv!4Z3>qOY7$E#W>exlnGKS=jc|&ukiT`WiIhba!j(W39|8MMy#*J=?17O4?~d z`TRMTjco^Vw0xT^>v8L==Nk3W_lzy(bY~r!_FwFgf;!_ab${M=>-(*%;B-!B2DX-R zdxI(&lHreG^VltNUwisGFi;?i53Q4~)6k=PW8cgV%i{hz&aj+z^40haWb#Q&no`_p zZ&SkrReB3qvgv8WoEV3x=L}wt6u`3=1QEE8tNiszFW#r`N7pBb`0w#u)rMz%icTn) z-fdp?x7IK!%NH{@N-Y(`6ib0SZrZek~e{;m*I_ z>Nq3r6x!i|ZUfS!aNZYY?{G?t4d?l)j(+S0I{5t>b0c z?M5PK8N68vZj2;fxU}rk8E!} zR|_9Aa}iS$KqX6EN6`EhnoemqGNlYUqqURYG)L|7^YPph9Hxn-h-S#=7Hcb#ca8gY zEFyl-JXrK@G3pL*6_On)ZWO5Dcrj5td1AWre=Fcdp@$MzUcP3JxfFRF#XoIt9rUzD zt}X70vPXgJ{$|mQP^AI$8QtXm*Yz78-MD+-jccL=F?#Pc-tzi*2;3^psDqdvUaBVo zn%qM$v&;KsXq;(Vgwek2*EW!>)-io)3!%@6?*tuL;vM&TDz)O;U1yVT=}0-Hb9_ug zxl0-HMtAfJPL*B zbZDLd`KKJ|hrDTo>mfWIw}DcR7b^LGa>YnXI))y`pe^lEG0#M02OizxHBQhm_!Y(s zx)azvNK@dRO;qJl-RkeY=@-G~nA?kwCY~z`3UfSRt@HG*u24w@YTbU59W(o(G}}u0 zSq}d7oj0gw=d6aGTjM2Hu1FkrpF!A4UcE>Y5d4{ zrEODHC1B?7^)iiO6WxZkcW7Z8$kKjLylO+$u-HnWt?zT~R^7NPHuWn*-Jmn<^iMNo zzBL#?tY4w4tkjper7?fZJ1%yAq2;;tYi>tU;^m7U+;HD|tGB5(m01HGpz@i z?_NdLYr%4!uwe#D-H!F!%e?~S8wLq+^b9VN*~EM5WXjnG*oMjPjo1g?wmyD?-C#pw z1fPkO?OXU^SpZw{l_QH%wYPN=G?Cn$T)4nrXebNKz4Ry%av&Efy~r~L=r%Nhvf`Rb>6{+jgqLGo^h#C_|cs_94x zQ|1o{tXx5Q2~qrgzFOngF}Ugr_e9&SmQU0VqJl71=}4-3y{)nH2T0hSl*f~c6LBzv zl6k|`Lh;cCZu6j9<0t^~cHR5&ifjI+5PnIxl>M;2dpYoT;Tg@*47x^ zET#Q$!NOiE^qcf?qgRa=vF(tiz$qKwF6mOVBJ#QpR%y}TrS7aBkD>evqj-M)F^UCW z+ROpu97j<$Ma%6IqjpK%I|`R`gJiT`yJbuc-g)Lg9Hn4${sU=wnY%S+rkOp zs$#9&-dl3}D)ECPCvJRse!$ExYL4V}zI>$__xJlM(vm@%B9cLQaftm_g5|wz=#q3L z{Dq+N*kVXiV6TmDSJx`$(oeQAZR%ZQRdM;(zRl+YT=JISuO`-88uczD-Q9Se*D-ag z*_>lr>My}xQ>Mdk&W}*f3tJaUMA0RJ&kAc6W0^i(V7QaRU#^tMp*!oRXF5G*w3Q^c zRm8vMZ+sO8%110koZ>kXOQYJowC6>uC#e_ys;@I6WIZ`6S-_iFm$>+>$j!% zCYgg;!k@sL!_4ecf1=H2{Ss0nT1&wWaRhX|A>l&MS!^bxDX`t9JUhn}+6EdJGfqLZ zM=+r<;)xa^k~U>5o$h%gm$-P!?4>1`$pJut!usvX+YFcGV z-D&f93khH3M&6G!Y{!ggY~7Bc&GVWKB>h@M`0U%7MY~^6k6%`w^u0zd$sO>}Xgp87 zP(S3h%=q*gOC8k42Dd${*>Chtk?VAliyT7lw}r)?7M(D|yf>A%Ma0{CZ`cr+M!qCA zbJ#UbmYH`+m!NkPJG2^-IZ6`T17)gW$}xob3_vwF?oBw%^}ydxuvG zadD~ZF;6PA&ity6)T!+-ZV7Y^IPn!kxq(YPf`%yQ%Thx1^VGIubZT z!pl6ppIjxTXC*6Z^ZxZo^Hb1{%sb7j*R38~U%723XPsUib=KJVLzH7HEW}OTk0=M>Iy`l#?d-~b9qh}O+#tT_NCE# z-ANMEOp|#HkK~f<_hRx#_b!A=ggP2Nb@)hl&@$OO-f>#z6-)Xje4o`tcky&#%i}rB zu2a321XqM#siX;Iwf@;Chx2kpNW5oLcSKw<6C7vD7VQjmj##{Fpo5C-7}HJQW6S3* z5#9b=^pCuoz=y!U#Mg|APV@a-;y2TY__}#~pB9oV`TncZe3B>8NehL!)EXl{<0WQz z1M`C84DQ7Us)oHLE3}raTA6s^3H+EhcoiFb!|uE8t(yJsv(}iK_*m-?V87PkK&K*z zvE)kBGc6#JSaxggw{_Sk6x%BQf@;3~dEwS+AL4q3AItJrZy(RWoYhwyGYVCiZrod< z5lRwi-XKHLU>ssVc+~nXZW$W#n5^j9NUZgu$CE4S^w$BmH?frXLWC;=`Lp>)%KDci441?d zOLIcRno4Y5e%ANZ%@@jHdA5zt{ZLP(QMX^)xx=u%gWOg|yW`Cr=B+Od-=us?MUVu% z9~jfD*39vz{pxqEL!s#)DRPQJ<%kA3{`4?qDORFyby@liy;w#0O6=3aN%Ijcbo@cZ z(3Q*PHLnWG%H$D3vi(_c*2x@z5A{V|4D?+LZa{jPi{C8RTsvx|zoZA>v2%>0I6J^> zHz=jPg^8k)#}^B&%tJ&GdTF@_I5S~5`pTQV5B^v5VmTwsP1(vH9BsF7_r1O;eP4yy z(r%B>0dUCnL1k_=Lp-xjqp;=iQ5P<&AXTq574t5dI_rmD&6i%B(uF-t&ntj`Ow&9w zAr<5-BIMi)%9HmY`OI-uXMExz8MjB@gxa9(EV~&-j<}e%Pn#_@XzryGmX~!id-x*q zZt}TQKLWcgU!G2U7=&9R&W?TnCOJa#^B~LI4ep*ywot;{=O;SLqodTvm|fZD6FoQP z#<_aZ7YbYr!mnnC-(R0YOtC`)FF*&P5j+EX{BETFo@0ZF=8yevn2q%92@9bI_U6?K z;fz%#`)ZkB3f-VhI=%Z)rz!a#CbPV{M%;o;IE_p&xt$)TnT1bU2^&PP+@!1EnGkI= zS^@K1fLZ+4GlgN2f(Wn#{lI9Dx2@IS{0Ct+lSDXl)wdM|MN1$JuTO_AiWiO+^ zul($boz(pNpWEe9MpwjvD$~+*{U-*_{pNOKmZ$v*qhg>5bO8PQJ6#^V{ApJb>N{4-Qm2Sb!~fZZ;+q{iRIVHldoO*~F7A zu`SBYS1`K_Z1D<0Y7T0P9`RE|QX~!Hu872Er&@}k_2|fH(1W-uzZ5DK=Emmsx(F4M zZN{Z>C$ci(`{0&uKKC@vMx%a>?y=Y1ujV|-!~ST2_v0{nci-SmCwv|aS~m~X%st}j zOKnq)C4EIHD+0eowfaZF6GbYh27Kl@z0*oMOLcq3W=n~v+=)%7RVQlceye(76KcEd zF+VmO@Z=YwDbTqTeI0{}r1Nupmc~4d)D5}116i+qsY_qhXtFg-&fVmWUiL|v3D`+- zTB1K&sU6-)vczvfv@Z6vTp$RxSWX=fDn>F@p4d$wDAvk2ex9K$?P z2SwCFCMF&zPmQm;;WuY?dZlVaeCpuux!RMn2W*kuYnxk*63^_DEs}_>u+2-X_!_zk zI}NytS9UX#c35;&5&6j#bQgbvv}jgEwULYWG025yx@vqQqLH2;G!B`Ts7^=h0`u%` z)Md*#eRyU+*FkBIx)r_2i|7-u9f8Km3Q9Xum9V{c_eV4@io`hDe>)hcVVTbN^Os31VqCh;0#URa<+(CFX7{z~x*g8PU-D1TI8d`yd)^ zH9dDbPoEBz1_t^BJ}fb%B_TJMHu zo1ztlkOk|HKP?u0Fdu%JhP8G3E5gLO+n0f#AI@4vgpARANOyMFS)9*xnv(!%QbXr~PLQ4HaE_EkehU7r zgqB4$CWxY9Q(4Sh(>qY<%vxa|l)9}fX6}Al@!p<#8=%BE_K+^g(iO&MpB~_kXXb{9 z4;{AdRPd)0=&CqorBcl>vY;YwcJCwJ;l$jRs6n8 z^g&1mU?Fn-AZaa}lSinhA-nIgQ680Y4E}vkwJ1~D!Ye5+E~UT`)JRaPzinU&xkrp! z9+{ciaUbt%PBmaw+{Oq{6nUnuXT}<+YA}(;p!j&Hq^PKoU<@Elum7U9yRI?76yV5G zJ`DlT??OlNMnBEZeo1_k^MhBTh+5c@ZBTS(c1!k!a-&HR{o^YfMpb?_c1ozuflh5j z*~uul$58A%;`29W&S-@1u}OJYFV7+wZ*;TYJ)|YOQJf{8fcUcv?NN`lOFVfuQhx2v zl#gwVtK$}}zquv9#4a>(j_7P~fY+gdC`zY6{#4~cp|el4DrrxE8S?DT0>q$zkQnh^ zrax8(N?Q4F$KTL1FNAPMjR8uXKh_Bvlp47#EiG17cH&7?0Qu<}Pd2oT{$8MPfj`6c zryWvqZdW*rFOQ6FWJWA~PjtJn;uEYl$n(JasQT)g*!5j9S)QPAw_LB9!2tMC^;?RO zr~vcc!{8lQcj>3;$eIhri%x;uAu!RO*(7ef;=P%@=gVN{C#mCpuqD^DurR!r;c&Pw z!CO>w?gk^v$xR4vP*B?5F;HeCJwTYVot~VDAP$A`eLL73PNwf`2nyaoSoO6i+PBT6 z5nnqa+)m|sm5FJZ&}Xr~9{bT#tf)I+k! zKrCJ!aFu&JBc7I)l^0kKV`GP?R!!ZZAJHHuH}<^mmDP_0B>Y^HVq%9#s)AMnY;A<) zWv#+VCpK+ekbt^-ElMz^GnO`2eu0=5uyHwB4%UJ+iUF3Rlkm*USZzhxAOo`$H-7u{ zpZK+`^E(q2zQHM%mBn{_e|4{Kk;(D|5GSk6v;O7SB-d!_AU=zs*BTa-0{?_k=jL|^A3eds-RoJ(YpYuTEe20_@6?+n1^Lz zj}enfeG3L=nV?A3AlvN}K%hc4bjq14n5)JqZdR*Q_Gse+&{PD$QgE0BQ50kPtkz$o z=p9Ac@2}7wf+jj%*r)tDU%BlzMp+J>%c_0QFb2zWLW4*~{PfR7=Tf(hFmsK7{JC6x z$47DAE~1P=Abt-j0~ihJx783vg9>2nN{}LTB33{zqHF)EsNUjxeg;v!#YZ$KvVsVb zQ7nHdr2JjQyhR#HkK(E+`bmWIcrGf_XCJM$y_*+BJ$#JL)&p{-3dhcM?{d$HzAm#) z)N7k;J36rkY&qT!>D{jvd4stlRm@!B8;p)8R4v@GBo6q3!MW(2VFDsgB<~~Ynbtf1 zl<>ihiWyaOm@p|>atkTBKS&;>79VbaH0I~cXCbx#SQ&L>-hrldqMmTIU{ zCW#9DgYL{9_+< zT4th3K)Bm=nAQJM&@P0IeTiH77Wo`i9jhAa|-IB_|7aTovz?Db@Nt6gZMoTsBFaEXD_el{M5$|EIGIsn%=tC?cF_ zUoP~~MrxEZ{bggWihy*KV`b^cIJ7|Bs?RBszN2H47lCuy z&*J34bFhWfW1zQ+JkA+O3Nf%h`N2`c{EmIjk7hB6&=l?0yEoaT=p1}!CRfg0EcZe$ z4cRpiNfkwGr|_r9GH?~ewcBRCsvGvEa-#O7S_N9%>Dp2pvu$NT!DBc_F0T?OXARxk zjuAF&_5JOfDRC6zwQJFqPpd-A@&)pYp)5t^$-XbD3Au>;+m(@?9v)Uo*&JZ9##d@Z zwtcV3`W+jwSR~LFA}uRx6^^VWRTXMx|F>>DY2Dr^R3~U&))tAZ1$-W!8BJ8={6ek| zFe}RG1Ow84JiYnAot>w#E@-qAH`*NYsc@P<)UqEcJ^LP~SLH(}0*9HW)_rqU_V{Um zANl^426>uxP-u)ldD|-^wEE49>!)qAE8@v*fB{_xe%*qC|*nG*InoIH9;k78&Z3_zJ~|~J?Z8bVVMY+FS$<_&%IFFuM4b}g~dc9?U)rM-ah-D5}g%lHNg+o-NNC4XQb#OqS#<7PFiBLVk&)AFQvF)vj`*5CV1!U5k!pbiD8f5Qw6orkEb?mj-4O2?`;^>JJ-*)4ef zpptuwC^8?6dYzKhhAFf$FTdN@GLf01N*a`t5P8n|a;$W;%0`h)YfN;{7E<-NQrTfF zcd_1A-a}a%Jw#=jC=Z6%N?pScZX9OIuSq$-Y!G*Tl_Yik(4y*v>*ZajYwPFsvZRL3 zq%ng!$i3-eQtrJuU_pkWgr$fjRnlPAq+(uOBd`c(QEgZq)M~zUWjE>5yot=|?o=z_ zUP$l$4Uso6(N=R|nD_{7m7Aj8Pm92^)WKqnp(GPHHwyy-<0gOEf}hSuA)?SKs(w0cCK@tsEV zJFTMg#!8s+HQZe2Sg};W!R-#fE`3=hd}`@30>39JO#Qi6z;ebU0ip2KTF<$-No)Fg z(Zce)hf>2}V14Z7594ab+WFzzPi@~Y#)+yLHq&luHq-i64)Hpa{QVWNBj4w@Bma!n z?iGC_^_0TPo&u<@w!ko))-@#bH- z@b2=Xpry;sIM(DY8+?rD@GhL&X0c^+c+^&1mnv|nPyzIif&H;K$>e-7X%Fy`_Qi5}RenuDlGs^hQ;H{Xvy{-+ z;hQcH=F5*ze4U`%WU3|AZ|xJ=aT}tRh>4v6IZ+X>q3Z=`4jQy!sCnwKc_lRaboXsLV3)o$6W-?A{$5uO@#J>I zfO&w4G_2x5s>m<)4N8Qc|0W{)7y%sQM1ybQk@#Xj;HYbw%m>#ro8Qw$oI2vSY3w&x zWU!bt_7e~@Ng_*c|MaND047GG@m6kr&tkY({#Ob2{jt_BfSXT`t_A>C55UKlM1*AD zQRjTnJR)xm4edCH!wvF{&DhETvp>&yp$!@OiOn3Gi{OblfLLbY7ACoMV8rS4{ z$&0_T6W{(?m0Rlzas8m{sKgfR5#Xz}GGDwCOjl=EX3`CUS8K8pAA37?ulgFXxwpip z=ax3RbG{hSrSHz_1)@J+>n#V<-khH%Bm$_1Bs09;49lv>V*BEIpUP9gkn{`joFmra zWu7^PAvounY|E>%*4<(IWe$Lr_MUw$eX69=(C&bM%ERm2?P=4%A95!O%fXv#n?vLn;3K^a@cmjkoUwE8^r5F3pN131bMHI)Hru0 zWD4ne+_~cXt+bkU&8YSeh9z~CzCX*qkRRB6l>YvW26-wQB!-hXCa>Vk|7l$K{njRa z#za`N@TkvuH~DoAtMkgwvWJ+Yb~xbnenjzwb$D2M4(1ch%XURc&J8;cZAASoe= z%WN!?`ge7%go?HdJa`S6ixv|s3Ab9-+2FMzS7hKM-Td&baNJsiM`(Hgafi3B!SJ1C z@XCBK_bLAUz*zpteohE*^$PRWK+ucktTT* z)_#pza(dg=5^4jsQT?DQ0n%BMB6&SCYpR~oo@>NXuMwyeFH7+QbKd;r_ zSGp)y1D(i#x4w%XovY6OGT##FEE9el`J>c${l{emF%)BOx2@?+wQv1T<6rlFDWCWA z-!vJx&@LVU&pV$XVm@v*TjmX(FN$Kx7jt|=A;eEQ+ixCEiBj-IbR~?SYiA?Dj3A%+ zik>m$)1>AFrZX(>cdkNqPnufCY`7Z7Qr zkgOgs^9zzH$pl+^$tt3NIZ}g&a1kSVjdRA(5I3I?Nfd&*5%<>$^FMdt4Y^%N7o!KI zaG@iopze?ue-Z`vKzP_D1)Wn*u`Cb#X&kR2_L|Ggb4<9u;TLGaoZ7$tS;`;*L0epp zG`pibIFI4v@ekX%wC6q+U?Hjz7*RDpq|Nmy;8v+pqgFE6BS-dS4jGo(@VIvhZb?<) zWEvNpUwPhwO7a%5=T!Mfrvdr1Uwr<(rngmagTAZFlGnWoxKcgbF0&0K7 zI(qm8jN-mzz61;|nYCykG3L(lp=K%CA`RyZL$_7Jn1~`X$uj?iN(dx1UJu?%ZLX+1 z7==l4lPR7bs#K;iOG*87q4rxIqYxV*iIjr(h8fbB1kzjWbo4{HEQZ?Ys0`FSVV=ap zGIyD!XYbnYtuf0(dY8vkolGv&6y>OKj!t!0 zXM&zG@A<{pn}KMWD&Js3;bmPehXmBz`+?$@Z9f^K^G`lr-uXR=BB_IqMPxG$l6$pt zj-{)<479A5nyV9%Fg9j&ClXS1-CM&t1kzlnRu_(0>uGAEm!Rw(jMN1b2?l0i3p7-P zWs7iB|JX~yk$>$SU#mwk9$TnK6-G3+sLMgeI4G#Nz7K-R86g2^1|uZe+8KB^*fXwy z%WMmOdc=l3>F%qPd1w?W8uQ`^Gkgz=U>_+#bes&N!hc-~Ww!Vkpo~kKN`=D*L=fz$lG<%RPWNgX*eobjCz zl<4a+S)c8O|0AlD3J1_z9iSzS1}TRJcH@iJ8Q(v`rS+7n zIn67;kV0pgg$cM7L6WFqI8!>r{ekh;6RWe=DJA}@^qi?8X0H>q4KJc=|Em1aK>X#> z-1CaaM>?x`k$Of!)KuMcOLfLCV02S5eBthFWv|ONru>r*@0GgRw%adY2ZWIj;HpYM z>j8Klhsdg_gm;h?d%PtS`_>CV;lUnr=DR$f{DUj!2kNsgNGL-KwHv{@*i7o+M~oPm zXyG0qV+ZPMae8LRxsext71{;u%-IZ?sv%+*`NR>ZWR*S>VqxJ9w*~E#jz%?-F56H^ zWKiEaQY03XxJ)?HEST7xh7d=R5oegPEWvgL`^KnBp`DIXquddq_yXT#816ovFsCi_ z?cV+#Pe(i(_No2nd6a(`SiPrUi7;?3)?N@Y`(tY^)J2g8w918r>#S{@|cZ20=tnR{|g?x%g-lY2GR z1}#(0Uv9>e1#>~NYlqyE{6k*X6nl^Ha(DOZDiJkLo5f(SoF6JDHDO<9kf()nW0@2A zFc;4v-{9AQE7zwtR_mxe^9iCxAbPLkT?0|+)TNaRfI4~P{*#vq!9dH5et-Tru|&xc zoO}zOpM*sAZ|xmXOIPg1PeOvJyDG`XPE@P|#=lMx)pipQ^68+gDc`54PLLorL&Wzf zrPCK;ZSi*(*xx#VC_pzB=?n=kcLmhL#@QjOkXqOaEjn6PvpadcKUV_Gm4j#p%eC+` z@7uX~(%DYWwF1_*^^|X#NgKs!v+wvLw)Sqon2LP^f9a=Yo4QO7$eC*sUVS&W;qJNm z{A$^|VpUS|!-dYfj2|y8)OAV{Pj8FMBIWZhG?Fm9A7P*5bE@^?Hei#x?QDO382tC? z8|;Za79A4g{d}d0C$D#bU!#0Uj&=+t!VZ-&0`(E20lwn9PdA^n0(N;6>_d`+H%&kS zws{QSTX!TGjAXShrOp;_ej3mFW!f5~1((eO#w~X0GW1ZSQq!ul40SllS~AQbP>z_{ zPb%!TY=5bbF;4p;kt-2<(?pch{$<()YA2YuEUo$E0TB*KrKTlb0dvU?devcG%93MF z&UBJ{0fX~+vF-<{j%X9rFWN%N;xZ>jp}@%{uKf}Od#WSOQsb>9R~eo5afRE6@|%_s z(E&s(^BiI;P&kJzh+A{Hzd`^KGm{oYd3JTNva*0~EJIpUhQMpVKOfFQA-*mw;OxhV z1f+t6cuFa%KOgP5GnRj#=l_9p*ZmhXPVg&x{%)Egc0cNYmNUrlGK0Cl&V0^0^x?vDi(Yohw zdc+payv((aCeAGcTn{ddIm_T7N3_SyG%wFeW?Q(;P6P!*4rWmnj41jR>AyL%&=hA;y*NAY zI5%YCWc+j^wjHp`bJ3oQI^Bqkt2(##fmTS}jq1YCHq)oJJC%8@dZMWd*?CH^u>o`J zM@u1lS8{Bm?Z?M*0Ped{6W}-zSSo`Jh!8are|GYVng9n7qQ?1qGO&pvxdQ*R0|lPZ z2OIvAEyY5CLxaxPDKLSvWRshhZfR|dQ*t4#p|1d6#WYK0d-c#gZp7BTs+lc9nJo}()=|_v>o5M50X5RGUvf&&xbB5ttX@Hg^MMby0%YqYl8TONNRQLAv zW5U!U>`ordd{0AWcDVc-RBrBtqw`=%3iq_gi%k?u%ZUN3R@~k0cN8xx?eXX3zdFj_ z1Es-3w3Hpq7MzRa?Ptf*yC1s&q4OZ;tu^|*(FYDaBiX_LtXj$-9}$fWQ-&}~tx2+$*>71$yz_6az zo;(0=&F=c!;@uCJcV%ehT~iMpfRgVec)FrP!W-eaj$x=SVruKl<&c~B zG*6FB+mElF{Sb2ljSAodRvX7j6}d^iyzY^eEx0x$|1e5uNCTW9`4QZW4ZkEMM@#Ge z@uh*Fl_Hhd@ev`j;3|F*WP&AuI zB#m!#xBgI+F_QBz?61QFK1g7Adc<>jynX_jU>nyjY}Zh}i8E?=kj2UMZv5fU1zlIm z5qshLJcLI%xcQ;+_D3WqOt%6ba-zbUG#HkimtO07W>r9gs`=QP{l5o1YntX;TerNr`6YI6NIy^Gu2tYqfd@%;imYZSOa12}8wVxK zqC{wn0yQTJ`GX^)WfoXwF8tNJF(Xz(X~dehNpw)zBd2i~mgu@{wdZ8dsQhXJZApwx z61gZ2@ zrXIrs`$GORhnk4E&s+b^$)~gxt~pUi&A(6z`zwlS(44u=>|Z*}FhZ$Y@Ggdf&BHH@ z$BySxx%*@1qXIw{RV#)2-%q}N|4CLWbA`Ar=)&YDFdiV-%_9dc=lQZ?a@RS9o)uUm zx9Nv&HGty9dSIvbyf9tB59R(qq$$I z2JfwRoYEjI#c&e)X73%9FD*ZFHj^eS55`J*S3H#62=zioJ(V(2*qre=629utr23eq z)oMRn+fUr3%Yx4l$stHP45G>1j0G*Z;pxdf_!&mms&-)|y}J|l)lu88hzP}Ja4TL% zBx~c$o1;S2gX=^@t|^~nnyf6%;~fl%MPT4gf8xbWu|>cI;9Dv;vlK^+wtb27 z$Wb#+K5E5VVGSvSaV5iPY%SE)|CO)g_1cG5MhfD$;t-7tLejun%cm&LK7f{fHRiXg6Hw)X|)NMBhcdYQGd)UeCBkBRSmwZE3v} z4ie5L)lUpU@!aK{^)6cE6ZAzCBPfSJF&CiGp+%*lt?0S(hkd6jVaF|PfVFM*ecbyr z?1q50(SCGgFUSKQYKhjUYRjHf5wu2~sy5vd`;yBbc`mju3; z%<)DXM#hhHCK=kpJRn;B{SPZ~C-{aC;3`eBWh^}XX#2;zVAw@L*@{bomZr|YHUnun zM8C9VrA3ccW&N?2=zvfQdo(26sLFUN&CI&z<*7gRr7kHfwX1SFT)Zot-Lsk zY-2e!G~WV>*@?Y?zwr||i8k#Ca(EujK3g8+8lEt1tuHID>J%aOn8r2k8Ak?CJi=3| zvmX#o)oUOv*vp8s0-HKq4p%xzJ=B6Hwv!Z+qnxE_9+R z(NTZ=hzYQNrSX$qd3qPF;_5I}Ib-b)mD77N;UNzR%f=XWW%IiaYduJ{F7jC3lQwmJ z8Tur|3JSA^Vhp8CE0rcZkYhbKq|+jQ*~zK_gIo;`+>`k!bn7qI;&}ctxF)10SWQYT z_~uV~Dq{N|zQ{QqoV|nK$B94FEY4MD$6ox#fdZOd^^KU?`1maB*)P4c3JzXyq z!L?)G#qwNNo$9wJFHaPOrlr{mIs<2^?DQa$<^RKf|m)Af^Aq{ z-E=Uyl9c0UX^)yK3x#eFitB$OPmva)UGt1ei4;@{Oq(}hveRQw3>a93`;NxuVD7k} zexzYxR&!k3-1zMN0|<*G+tH&SjAgaKBhx_Wp?e#;b7!YMz~ zTPh^SC})%_F^ehTNb#{(N@KG%XQ>p^aK*@*?j?rY=8W1Sr$I%(gjJlxVO$XF0Ww z(`);+U?7~gvWmhqVn^~$RDfK4OB{$$Y9?P=AWiG7i^_kE|DBiF|p~ zDI`wr+VZafClroxufk70vuI<#E*Gt`ebQ`sG*Tr&OW^(c@L?UzFQ26hu(3mjSjQG< zA{WDp3h1Ck_qvBFE|2)rMhfp4ovzD-0#`35Tb3HH_g9K68@V!5l^4$}9L{mOIHA35 z=MGC!x+*e%9cse66y7?Gx z7JBr({e;Z>nHN7QFInDjP&?%tp|STq66L<=pUla0`%~z}RL}6!w^5YhHJ%0c?|JLi zZ(lzP8~TpEWs&htr74{sBsrdEZBK}CMPdY2FL;w}r9(R^j0fBA%@g~z2PJx05G z-TzU(a_|fdvcs`eJo+GMTT+Fm;t_Uws~F5P)mW{|x*>1@-hO|3?@j=GEN3M=v=m_`)pCC$IXZH{05T5nVn(EaF~^J%3%|H21kqp5@`C=>H{) z8Qdj2AsJd%!Y^w2qLg3Ml<}si)lJ0m8Z9 zEgNt#R-zk~tTtB!VX=6XAJDWbBoiL88DLJm!@iGyI}%+XD62Qq9ICE-C73Z({g6S}pflMBJaiwf~>h*;`-@gWsL~n<`{@rfxF!QmiF) z&4b#vGtKq=QTd_Asiz|~p+KENx@bKWEh9l$xN_G+Cf2L#r;9Xf2m4Lrsq3*KoZD9h zLFxJZx82v?uxwCZg19(k7SyOSYXze9A%aa&oqA3wvO2z0S5u5Q9uq5(>9(vy1lQNw zL+4ByqdN6czGg5I6Fds4anjJ5G)AdC5{Prvcyz|jC1%^jJ0P^GGXuVdYk=`stJxPU z7Yg+)$+!StUm3JLIp_%eMuW^(uuq4F#0N*Y>mbp3!POnu13LWi1TAK;Db(m*^f6zB zrrdJ%+Z`KRX~KPs1k3v9>BtDUT8FT+^6)Ay{R~#4^Fmt4_TjAlxM@{DY_kc0?zry1 zk*tsEi`YJ#T^ScR>%`bn{9A-R=Qn>Y9*l3b`cBNzy`wK|;mj4x%W31+?mondg7@Nv zVq0Q^dw!@X-#7b!lMGiiU*moWxN6oeYBsPtALcFQFrhR}B|oFscr?3XDdN5VW~Ih( zdehP_%pa8L(0Z~=D%}xqJVs?3N;s%wYCLMO{~O^@y-vY0qE=0{XNCoxiTbb~X4|{eFLa0#tJfBa(JQoghlrPiZ{Es3pWnnGQDoh*j=PN~?`sY1m*0Jf( zJU*J%fMbY}F^y~i~1?jjwyT zL~97NwzGW<`QZ|tFm=`d!hu}gv=2%VTSXsx)YV*ln*Dn!3=g0jV2CC>Zn0RBXKDbn zas~Asrw?P=tHx8gaeq%&MiZ>(j{AOiNw@#0*$%Kb=oy`|QhLm>hIoqaBF~xDK zC{0q$`i1>y$%0S0#s#10XE*3YJVVW1EWG|JBtg-UsMpDjw8F^C9-d<#4&Ko#+@*EbM1XtQMscEl4E}MJxNu4*| zWH_-$KFfIFtUr<=*Vqt&mJ9G;k9?aT_2%EO$G+`UGTCwwP15AMWc|E%Z(pVL1T-O& z@_ok~4ABHBak-+E`Hneu=H#*iSmW-6p4gwi&TIW7koUVC+Sjq~A-A-`hsLOB@6Lz4 zLZq=YDHo;r6l}dqe5g8SA_S84De2hGLZkmHK>A-pr;%B2$+S5#894tJz05@Dt*92K zXXn!$eq)|dIq5$AndkseFZ}k<9>1t+4u+Jf`zl-POI*jM_PHNS>Crzjl?+Q$QkD7c zfScqRcHLz$uV|bZn2ephR_^ULQ+C(pIs6UNokeA|WJ1FEyx|bhZ)obvY&?V3T$! zYgQIEWv^;XV<9yQn|M_~XLsszVm=PPH@6A8`~N4|Od;=wY5N3_xwYBl(4(bwhclx@ z{82wqLJm${z7Qr7?XF;7hobM$k!$WZ)OgDCg9iF5B9H7#U3fP%rEBKWgT`Kj`#6ZM zVMesx6Bz56OY@Y7dPgST8{j#dquq)IY7UGpx75)p*ug8A>v7kr%PwPoJHxd}PDgb9Ss$ z3d$ys_L_v=-DewcOVT^vh<-R?f1*-!tQwVT1n$Xg&2P)P< z=CW)u7X8}ZD)&7q49?u)$}`58X!zx0?*=e^8h)i45cBa(IxPF@yi*raXlT;WE=d=X z`$*!`oBtU_<=I8EK({jcr_g+UeTmG;qsihS5 zXV~XMzd6gvbr39cHWT&WYB2lWz+}!sC>D1w9e%v@{p9d;E5`v~f{}Sdl&KfR#0l@& zmfTETAFxi_WQerF3-wUGmq{AmWrlx+fE9Tc0(=Jvij3FHfzx>l$sC%!Sk_s-h9zQiSM zsw~mg?K(JA-YE97(YC$h-;vlGzPWa@55FnIf>zj|nKKs)Js3Y-JAG{rph_mR&_xFc zwXvV0N_s_Cv!6y*G!t;Xb$c4hvzc66DC!EFO_|RM>Aj{K?7v?o*xnp$3U;t-7mFW} zNE|BFzY<`=Y9wtpn0dGFvA&ngcXW?srkCDq<&YRY+^e>!2WP&&)kO5sq^7eG6pqhw>^ro*ImUfmzD>~mcVx%dREOp5uzj!7R2iam z-9ZqA5R}!!ujPLjDcuC~5r6a)4aWnx%m|6b4HicRxNCnv zSfB10WtzU*u@3~6V-g&qn(JoWitbeGX<}?7+JO&9&^0PgRgSmtWZutAFbzbptA%!0 zc<=p-e)|>17d8kYR*6AwD>cBGM7*C*J3coAm_kPP>$<|7tCMWAtyskeOz#?UU&&x! zV1NJAA)J$jbM2M)+Dq7q5gPWLA*QRKK=Yea+8M%QyT>DvKcpR;WofNF*V@bUp6y%GT-sNdI7d5#6< zfb@#OpDuOmt2}25XKv9e{C4(yyQR0p9uXL&4z8OihFUf~=m)(oGA?bbm{1C6*Y)bL zMrO&px%^VKYkd0rb>11zjdPM4I}%u$U2;fG=+U#&gOmR@Sh};p=9^$lk`ksm5^_Qg zVZnVg!Gz?<^}8sU=9_)@>#^S$4pL7`9RNp*_f$v7Dwvn0h=10{qUn(0fcZ4}H@#0+`5wXRlp`s8k!r-A6X z(RqWK^CH@XK@rg($sSE&Pd8t(h9hW8LCTswn}S(US!ATHtWZ_d2z}Sux6>M9&#SU+ zfXu^hr0&UqlRX9S7{^NqBv~IU{SEAa)(@Hi2~;}$t-~&xI3~be-zn2|fqoiP_l-6cr4zWAo}y9hztM<@yc< z(a4?)IKPE5mEvdK>tmJ_^Dp?$rb-y@_RcySXru7~I)PRXQ^}^?v94>R84YAetgD~6 q0g&o^-+oT`TKePelp!%?S-&1SO5To935;90{{s84>SM(z**@(?EoO& z;%Gzlq))CKi#YemR2a_N+zBgx#LzvUdD$CFIC08x^wX+$!LQ@$2nlMV%3h20t=E4y zW;V8Nu#<((Vt0R9Ctuzbn60>REBi*0=5`9W?E!g996;GD+z@3fRbGW&^=#{4Xg(4~ zDDUf)40BsR9hA7tCiC)vt0(UM!iN6=6aaxIPN_yXrR*Rhj_C~YL_-p zoxb}-SpSS{a$w+m3IlY3H44t6t}v#WKvQs1Km2IwV_RZg9pjk;V`Wdw0mMQA1m7-H z<))m&N^MUHk`hGuj|GQ0kSOe^DmUc<*3iQtv#`gM&#qOalL* zU++T>3 zUqrpPXLbeOKjVX3;pvpAOlQxUBBllAWdDTX?x^r(Jvaz!FR$=2Kudw91~z5!)V7V# zlq&JzB5pIbxSHbwZP(T?64pbwZ)CsKz>~Ze+?>Jq%Rm@9s&cjlbE1lxhxONDeiy3v z6Pcp3Lqj(EpdM@WX*SrrD*9j*3j-Tf>n0_r`qU9M$me5^C4&FZ4ZH=#9YaOLBkt=! zF8I#PnM)NjgTTiE(9g#6FF|)XxwlqpPqRK{br_0O2bZ^Jb{e;u{L%*H+SpL+KG0!s zvFj4)6v_y>-aPCCM!4$je$k-ia6;jUW^|W4*;BN|Ef@R4Xz+0Ih)yWMO_=$Fvhd)Zzf0Zk;Q z4{ip#2BrqIW5b)UzPE2we^PHjw#9Rw+f4@1t1Zs25s0;LqwT$0SEYT~JO4enJVWjd zFhPvDfwwSylU9+vOdm|^+}9Z+M5A;EUD*9$nio$^e{v--Sa`QxV;Iw{`ZNc*d>zTsk8WwM}TtfnxPQrg%sU z%z$if=j*?ykL^1FCrV{)UK1l~=!J=vJ;M!xVCH?}NC)6clVsXm`H5EWCI?Xsb=?MBZ$J`FmRr zVmSA>=*UZ$HHGH)Ez*-xps+3OL2)6=H3EK(6B;=ZqWxz`dsFw?#EcN=dB64ZndLjS z&aZ};S~T7->1I*J6lA>pBdISf$&>Y<6fY*#!Gek=z@?z>gH)E*o+%fOg4`*~)b&{3 z_M5&|uIh{gh)-Tjb~hdbvA#Se`rC;Ft~YAWFxb(ANa>X4sL4ykb9#k)=3TLZRcVqL zRP7^RC~|$g>DaQqlxBsL+#_v$@;f}St-B1jJ^w?KdQakvKBlRH;co@Jg33f=&Vt_s z;!=nWP`iqziWiQ-5p(#xH;F0KDVZjcQ|<;nIweFD6DGP0Sv)KTOxVSDj-!qjDSLTH zg#W8VRFmmdstKjsh8J$247l%XT5&W7^c9kx^2J6Z-`>o+RG#Xj=>VrOD@M{SG6FYH ze6~KY)jifi9jodtRp+KFK4n*-la*ktxo~%f%!tD7tn1cUIZJiGt^;yCe&{RcYgbo$ z(LJj9QR;Kq_5OlRryXZ`#^FkBG4$c!rHjMTz`YO)``@s5?#ZtYG$@O1Ea%CI$R-9KkGGT^-h48k`Py`a01Yivff7PnA(+EapNGCLS5uF5&|v z@EAMbSr$2dSiIfpfh&V)8|j=XmaYEUkmZR-6i9MPO#9_^6~NEZF*T|xUvb=qj#*G4 z5uw4zV@`xV(;ygM0X;8SKBIAo3KTzxY>&7TlNF#2s@MJfhj=s1s{_5CzuapbWkFOs zY^kanTSKzZ*g0~sr$PUblA%Q8Cl8O$v7#VD*;x-)>J)6KjOsqrz=UdFAxutz93{ck zTc7TK0lxC+iCDC^^|rMX=ZVGedjBIEO3Hu+7%g<>C5~?hN_#L|F*(;-1b>g@`VjrZ z#NC6Vxi##=xqYR!>LsvPVen{Jtu*|&FtW3|DFVMZYu(K{Sh&xAlYs3rfxG{l2&PK) zv1_dQccz!B(JIdUGS;sRC>H`-zhPQ5&M3o2!CjYvDgXCxb1z3LZI(i@+F+xa?#h`5 z#>@^W!&0jncZT5BqSOsr&?FOB$f~ed_m}tb%xIPM(ir`5&5`oeeKuYcq-kYHM|;V5 zOE>2%o#?ZJ%2hdG1or`HIPuaqKwxUzcE!{2?9spN`QCeR%Jm|8-0GBiP4wP_Phal; zxt%$Fq4CwrbqmA-0rDRI3jTQed@Vx|(AJ%kY zPEsH_9X$CfwGT}>IOBOYPD_A>GVq(aKk50B2OOL2&zqM>lx zDqJQ7za74VfMrtsSvTC73vYp}oq%;fC`LY~6XwQMZtWyp_;6G+prPx!gpxNwQLcMb zz%Ev5>qUY*KUASF5zZ+w&gi{E(y4?iSwF}gvS**8v6y;sI<6)1#K_2j&5VX6Bmm#LC60tLF8sYzEjB#Pi}Lw5RyKY5isJM!t7 zT~&wJ4g8oaZDao!#% zgq1m^i+3_4t%feo<-9i{@afoMVMir(Sf2S;+glFBPv(lr>Gg+OgwJ2wdnGSDpx^U3 zxQKaF*=YP0k~Zy*QYzD1m2I6lh04Fh|1z?O;#<6t0xFr4q4bBtlv{x$YB+gx2 zUvdvtA3bbx;ZXFQuVqrRi7&I-VC%zb67ULXI@h%`9NBvH<0+I1#Agz--#c|5d-;rJ z{axo;L+!+|&I;~}1cnx<*)X?#G3J7tq+-kw{V(6Y_C=YT&6elXXzw?KKS$+#I4ukP z!DY^BiN)GiMC(M3x11l)UwjKpeiwFrt&EJxR2<9yt-;enECfUOORnOL&m;1jIqq7r zo7}5^?qn7F_rotZft&e21^FcZ9nW&=luRoTV+V^qP*JSjQ4C(F^>5KRj$FnZCI4SrRLL))-;AfrvChM~MvmM`|wk7Y+{N2AqRb3azT% zZcPNZS!l;PBV(@tp__?XYxUMr%M!ZpOb?1jw%n(*=_%+Ry9poP><&K&Y-AXGc|S=? zA7wC`E1R|P0XN}(R>hGiaRl2{F%h9prq6}$wj^b&e0I^cPjdRK3pyD5&Y0o8mFKl9 z!|f`0qRkd>;|ihJVjv2drLvo|kA0RlNZotLQ?CZtDxh_UvSyUCDK1tLaNum48fy)iQTc>nESG4o`J=FN_2 zwxvyx+fioPN?^JeMSZj}%mp+(r~j{<8uyI2gEEze=DSNEw$cdsZs`0H%CBsWYu%Ij zi}|xw^#3o*Uk5D+h|nhzaWMsy8h7`!K>6Yth8z*tkaC|;WTX9_4)G3o%X6Ep82`#~ zo0Gn@%&?Z6RTqCfZcyH3XO(4`&%~&rw4?IFo?iR;Jxrtf3r0yKW@h)z6K@~^`=T@O zo3yA)^j+c`xbAyS^S;qVND1&*W2gvDKc0vhg@ujvgU~|XyJUWtk5Oi%hrC%_v}rIwdp7NK(HN`k`uP=TqP-_lA&v7Anl-Jc*q6Q-8PK z+-LGqVisiJH0<52D_;DIb4bB6fb_dq^YfCgngErtq71fuQO}2~UBjU6gX0t5^HDkI z*#YFsxHbO~mYga>2R5OFC6X_Wyq(1Vl}b1_pY}h2yAS%FmxNVPHQu%a{%nint;uw< z4=;uE4+4xwb(vJ_Ci}-z(qpc3w^1HzIO$sY4M_hW1M_MN6ZD^r!sAL>>1mC!eJr~Z zwDrM&ouAQ<{-|7tMwc9ZN1BkPo-dY|Kw0vyf4KHj*4p9NT=bf_u1+~Aw~m&* z+%*i!OK5_~!*!=ZYI|o9tKhZ+!q48E3YL%RU%go*_&4Lv?So+)sZ;)we4y4w|oA20Q>N6 zi4uxcjAf+NNxtE#2)nITR=q_9`Q)=9uG^Jnlb(vp5~SSY_PRzq20s2_OKBs0Aa6o) z3ZiSq#u1bjzTWs^JeW9EP_r1E`8>85`~= z>8+2PHy*t&M)gtB;pns*k0=3ME1~S#8DrtjnWQ=nV_A%Wl!XnhN~}A2x7rDy%Gfg+ z8bmWV_p1$sPw%Q*gt@l94wKZIO^;=8SAGW!)@;bO!Gqne;Izz2V;vn z0Cpl`n{76{;!b%EC!f&0nDFHZ*X);Y$l(D_B|{Lc+haj^COj!(0)X}t8%9`c2NDh} zf=2=K6!>SqqZ#4nybs(Hah{z12#a>~`or7txi%D*B!qV7H08YpxmR+Bf!Vkiy(Rqe zj?Wfb52gw3S%?40lm~WoEtXYu^IuSi*TD>^Ne?8Ri;;dAVn>EGE4SjJIt9rLK~l@U zND_+`gXW`!XeY_1O(S*YF5lh}kQJ-ipC$QfPMrr@oXZ*I1ffn?*rXihFQy;Jq04P1 zA5nU{5q%q02Z9Xgdf=P6}L9EcJRxX>FQl56HG_+cM4 z-HvS%u~N1xXNcTfD6og8N{u2ii$ zJ?BC(|L0Ba{I{^5-dGiR+-qwJyP7mSeLg`^K8G#LNE>-E3`=Ms=!=_DP7~o&yF}&T z#i(nl@J3DrI$vYyGLd(s_aH-f#sL4w8g-6hHMM-9ztB1)`P?HJB5t}TZ59K3`$T!(|8r|*l zS<|F=XwB%KKe&fRKkFqjJ~Pi`P9K;%(%3)yRD}W^nD$guziGffIJT2oPtWMhQ|!q* z8wgPr*EnSPiW^ON^2TL$V9~yo@K_k+7y&&o`#ek0`&;4I=Ls zD5)7Py2GEjfPmc3#QJX(=ZU=0+iv}*;BA${A!PC5-RB>BgL0v!;x3)}F?6;OELo`@&F1)t~je`4x`7&h3wNN}_kG=OhH7{Gvnj+%0G5 z*@BUTm?50YHA41~kc~a2M=Uh#FY=J;W#%w`vO*!ssbIY}KHVHF(9$hH&IJ?J>X|6l z`GrarGVJJDy%s(3>MH&=fo=;C+PZ;H!K_=#;v3;78ygxlZ)Lak>CN|#mRC$H2#DlO zk$R-eu8UL)lYIj6&RMLJ#6kw=5?NEG)~0et`EFP`ILP?$x(XRTft>D!8X83~8q+)+YHue{uF*xVoFuu6)y@ z(__=&PaoKA2kV5+YngRK3EPR4(d-fx> 8u) & 0xff) * 16; - ivec2 destCoord = tileOrigin + tileSubCoord; int fillIndex = iFillTileMap[tileIndex]; if (fillIndex < 0) return; - float coverage = 0.0; + vec4 coverages = vec4(0.0); do { uvec2 fill = iFills[fillIndex]; vec2 from = vec2(fill.y & 0xf, (fill.y >> 4u) & 0xf) + @@ -55,13 +53,14 @@ void main() { vec2 to = vec2((fill.y >> 8u) & 0xf, (fill.y >> 12u) & 0xf) + vec2((fill.x >> 16u) & 0xff, (fill.x >> 24u) & 0xff) / 256.0; - from -= vec2(tileSubCoord) + vec2(0.5); - to -= vec2(tileSubCoord) + vec2(0.5); - - coverage += computeCoverage(from, to, uAreaLUT); + coverages += computeCoverage(from - (vec2(tileSubCoord) + vec2(0.5)), + to - (vec2(tileSubCoord) + vec2(0.5)), + uAreaLUT); fillIndex = iNextFills[fillIndex]; } while (fillIndex >= 0); - imageStore(uDest, destCoord, vec4(coverage)); + ivec2 tileOrigin = ivec2(tileIndex & 0xff, (tileIndex >> 8u) & 0xff) * ivec2(16, 4); + ivec2 destCoord = tileOrigin + ivec2(gl_LocalInvocationID.xy); + imageStore(uDest, destCoord, coverages); } diff --git a/shaders/fill.fs.glsl b/shaders/fill.fs.glsl index 89da3e01..0848eb48 100644 --- a/shaders/fill.fs.glsl +++ b/shaders/fill.fs.glsl @@ -25,5 +25,5 @@ in vec2 vTo; out vec4 oFragColor; void main() { - oFragColor = vec4(computeCoverage(vFrom, vTo, uAreaLUT)); + oFragColor = computeCoverage(vFrom, vTo, uAreaLUT); } diff --git a/shaders/fill.inc.glsl b/shaders/fill.inc.glsl index 356248f1..c22125ae 100644 --- a/shaders/fill.inc.glsl +++ b/shaders/fill.inc.glsl @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -float computeCoverage(vec2 from, vec2 to, sampler2D areaLUT) { +vec4 computeCoverage(vec2 from, vec2 to, sampler2D areaLUT) { // Determine winding, and sort into a consistent order so we only need to find one root below. vec2 left = from.x < to.x ? from : to, right = from.x < to.x ? to : from; @@ -23,5 +23,5 @@ float computeCoverage(vec2 from, vec2 to, sampler2D areaLUT) { // Look up area under that line, and scale horizontally to the window size. float dX = window.x - window.y; - return texture(areaLUT, vec2(y + 8.0, abs(d * dX)) / 16.0).r * dX; + return texture(areaLUT, vec2(y + 8.0, abs(d * dX)) / 16.0) * dX; } diff --git a/shaders/fill.vs.glsl b/shaders/fill.vs.glsl index 20d04463..a8bc1acd 100644 --- a/shaders/fill.vs.glsl +++ b/shaders/fill.vs.glsl @@ -29,7 +29,7 @@ out vec2 vTo; vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) { uint tilesPerRow = uint(stencilTextureWidth / uTileSize.x); uvec2 tileOffset = uvec2(tileIndex % tilesPerRow, tileIndex / tilesPerRow); - return vec2(tileOffset) * uTileSize; + return vec2(tileOffset) * uTileSize * vec2(1.0, 0.25); } void main() { @@ -47,9 +47,15 @@ void main() { position.y = floor(min(from.y, to.y)); else position.y = uTileSize.y; + position.y = floor(position.y * 0.25); - vFrom = from - position; - vTo = to - position; + // Since each fragment corresponds to 4 pixels on a scanline, the varying interpolation will + // land the fragment halfway between the four-pixel strip, at pixel offset 2.0. But we want to + // do our coverage calculation on the center of the first pixel in the strip instead, at pixel + // offset 0.5. This adjustment of 1.5 accomplishes that. + vec2 offset = vec2(0.0, 1.5) - position * vec2(1.0, 4.0); + vFrom = from + offset; + vTo = to + offset; vec2 globalPosition = (tileOrigin + position) / uFramebufferSize * 2.0 - 1.0; #ifdef PF_ORIGIN_UPPER_LEFT diff --git a/shaders/tile.fs.glsl b/shaders/tile.fs.glsl index 862e8c60..3e137abd 100644 --- a/shaders/tile.fs.glsl +++ b/shaders/tile.fs.glsl @@ -82,11 +82,12 @@ uniform sampler2D uColorTexture0; uniform sampler2D uMaskTexture0; uniform sampler2D uDestTexture; uniform sampler2D uGammaLUT; +uniform vec2 uColorTextureSize0; +uniform vec2 uMaskTextureSize0; uniform vec4 uFilterParams0; uniform vec4 uFilterParams1; uniform vec4 uFilterParams2; uniform vec2 uFramebufferSize; -uniform vec2 uColorTexture0Size; uniform int uCtrl; in vec3 vMaskTexCoord0; @@ -542,11 +543,16 @@ vec4 composite(vec4 srcColor, float sampleMask(float maskAlpha, sampler2D maskTexture, + vec2 maskTextureSize, vec3 maskTexCoord, int maskCtrl) { if (maskCtrl == 0) return maskAlpha; - float coverage = texture(maskTexture, maskTexCoord.xy).r + maskTexCoord.z; + + ivec2 maskTexCoordI = ivec2(floor(maskTexCoord.xy)); + vec4 texel = texture(maskTexture, (vec2(maskTexCoordI / ivec2(1, 4)) + 0.5) / maskTextureSize); + float coverage = texel[maskTexCoordI.y % 4] + maskTexCoord.z; + if ((maskCtrl & TILE_CTRL_MASK_WINDING) != 0) coverage = abs(coverage); else @@ -560,7 +566,7 @@ void calculateColor(int tileCtrl, int ctrl) { // Sample mask. int maskCtrl0 = (tileCtrl >> TILE_CTRL_MASK_0_SHIFT) & TILE_CTRL_MASK_MASK; float maskAlpha = 1.0; - maskAlpha = sampleMask(maskAlpha, uMaskTexture0, vMaskTexCoord0, maskCtrl0); + maskAlpha = sampleMask(maskAlpha, uMaskTexture0, uMaskTextureSize0, vMaskTexCoord0, maskCtrl0); // Sample color. vec4 color = vBaseColor; @@ -571,7 +577,7 @@ void calculateColor(int tileCtrl, int ctrl) { vec4 color0 = filterColor(vColorTexCoord0, uColorTexture0, uGammaLUT, - uColorTexture0Size, + uColorTextureSize0, gl_FragCoord.xy, uFramebufferSize, uFilterParams0, diff --git a/shaders/tile.vs.glsl b/shaders/tile.vs.glsl index fc21ba44..9c38aded 100644 --- a/shaders/tile.vs.glsl +++ b/shaders/tile.vs.glsl @@ -34,7 +34,7 @@ void main() { vec2 tileOrigin = vec2(aTileOrigin), tileOffset = vec2(aTileOffset); vec2 position = (tileOrigin + tileOffset) * uTileSize; - vec2 maskTexCoord0 = (vec2(aMaskTexCoord0) + tileOffset) / 256.0; + vec2 maskTexCoord0 = (vec2(aMaskTexCoord0) + tileOffset) * uTileSize; vec2 textureMetadataScale = vec2(1.0) / vec2(uTextureMetadataSize); vec2 metadataEntryCoord = vec2(aColor % 128 * 4, aColor / 128); diff --git a/shaders/tile_clip.fs.glsl b/shaders/tile_clip.fs.glsl index 3d6d02f9..4c10f65d 100644 --- a/shaders/tile_clip.fs.glsl +++ b/shaders/tile_clip.fs.glsl @@ -21,6 +21,5 @@ in float vBackdrop; out vec4 oFragColor; void main() { - float alpha = clamp(abs(texture(uSrc, vTexCoord).r + vBackdrop), 0.0, 1.0); - oFragColor = vec4(alpha, 0.0, 0.0, 1.0); + oFragColor = clamp(abs(texture(uSrc, vTexCoord) + vBackdrop), 0.0, 1.0); } diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 35322543..237092cf 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -21,8 +21,8 @@ use pathfinder_color::ColorU; use pathfinder_geometry::rect::RectI; use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2i}; use pathfinder_gpu::{BlendFactor, BlendState, BufferData, BufferTarget, BufferUploadMode, Device}; -use pathfinder_gpu::{Primitive, RenderOptions, RenderState, RenderTarget, UniformData}; -use pathfinder_gpu::{VertexAttrClass, VertexAttrDescriptor, VertexAttrType}; +use pathfinder_gpu::{Primitive, RenderOptions, RenderState, RenderTarget, TextureFormat}; +use pathfinder_gpu::{UniformData, VertexAttrClass, VertexAttrDescriptor, VertexAttrType}; use pathfinder_resources::ResourceLoader; use pathfinder_simd::default::F32x4; use serde_json; @@ -91,10 +91,15 @@ impl UIPresenter where D: Device { let solid_program = DebugSolidProgram::new(device, resources); let solid_vertex_array = DebugSolidVertexArray::new(device, &solid_program); - let font_texture = device.create_texture_from_png(resources, FONT_PNG_NAME); - let corner_fill_texture = device.create_texture_from_png(resources, CORNER_FILL_PNG_NAME); + let font_texture = device.create_texture_from_png(resources, + FONT_PNG_NAME, + TextureFormat::R8); + let corner_fill_texture = device.create_texture_from_png(resources, + CORNER_FILL_PNG_NAME, + TextureFormat::R8); let corner_outline_texture = device.create_texture_from_png(resources, - CORNER_OUTLINE_PNG_NAME); + CORNER_OUTLINE_PNG_NAME, + TextureFormat::R8); UIPresenter { event_queue: UIEventQueue::new(), diff --git a/utils/area-lut/src/main.rs b/utils/area-lut/src/main.rs index 1c6c92f2..09110b3d 100644 --- a/utils/area-lut/src/main.rs +++ b/utils/area-lut/src/main.rs @@ -6,7 +6,7 @@ extern crate image; use clap::{App, Arg}; use euclid::default::Point2D; -use image::{ImageBuffer, Luma}; +use image::{ImageBuffer, Rgba}; use std::f32; use std::path::Path; @@ -26,6 +26,44 @@ fn area_rect(p0: Point2D, p1: Point2D) -> f32 { (p1.x - p0.x) * (p0.y - p1.y) } +fn area(y: f32, dydx: f32) -> f32 { + let (x_left, x_right) = (-0.5, 0.5); + let (y_left, y_right) = (dydx * x_left + y, dydx * x_right + y); + + let (p0, p1) = (Point2D::new(x_left, y_left), Point2D::new(x_right, y_right)); + let p2 = solve_line_y(&p0, &p1, -0.5); + let p3 = Point2D::new(p1.x, -0.5); + let p4 = solve_line_y(&p0, &p1, 0.5); + let p7 = Point2D::new(p1.x, 0.5); + + let alpha; + if p0.y > 0.5 { + if p1.y < -0.5 { + // Case 0 + alpha = area_tri(p0, p1) - area_tri(p2, p1) - area_rect(p0, p7) + area_tri(p0, p4); + } else if p1.y < 0.5 { + // Case 6 + alpha = area_tri(p0, p1) - area_rect(p0, p7) + area_tri(p0, p4); + } else { + // Case 3 + alpha = 0.0; + } + } else if p0.y > -0.5 { + if p1.y < -0.5 { + // Case 1 + alpha = area_tri(p0, p1) - area_tri(p2, p1) - area_rect(p0, p7); + } else { + // Case 4 + alpha = area_tri(p0, p1) - area_rect(p0, p7); + } + } else { + // Case 2 + alpha = -area_rect(p0, p7) + area_rect(p0, p3); + } + + alpha +} + fn main() { let app = App::new("Pathfinder Area LUT Generator") .version("0.1") @@ -38,50 +76,23 @@ fn main() { let matches = app.get_matches(); let image = ImageBuffer::from_fn(WIDTH, HEIGHT, |u, v| { if u == 0 { - return Luma([255]) + return Rgba([255, 255, 255, 255]) } if u == WIDTH - 1 { - return Luma([0]) + return Rgba([0, 0, 0, 0]) } let y = ((u as f32) - (WIDTH / 2) as f32) / 16.0; let dydx = -(v as f32) / 16.0; - let (x_left, x_right) = (-0.5, 0.5); - let (y_left, y_right) = (dydx * x_left + y, dydx * x_right + y); + let alphas = [ + (area(y - 0.0, dydx) * 255.0).round() as u8, + (area(y - 1.0, dydx) * 255.0).round() as u8, + (area(y - 2.0, dydx) * 255.0).round() as u8, + (area(y - 3.0, dydx) * 255.0).round() as u8, + ]; - let (p0, p1) = (Point2D::new(x_left, y_left), Point2D::new(x_right, y_right)); - let p2 = solve_line_y(&p0, &p1, -0.5); - let p3 = Point2D::new(p1.x, -0.5); - let p4 = solve_line_y(&p0, &p1, 0.5); - let p7 = Point2D::new(p1.x, 0.5); - - let alpha; - if p0.y > 0.5 { - if p1.y < -0.5 { - // Case 0 - alpha = area_tri(p0, p1) - area_tri(p2, p1) - area_rect(p0, p7) + area_tri(p0, p4); - } else if p1.y < 0.5 { - // Case 6 - alpha = area_tri(p0, p1) - area_rect(p0, p7) + area_tri(p0, p4); - } else { - // Case 3 - alpha = 0.0; - } - } else if p0.y > -0.5 { - if p1.y < -0.5 { - // Case 1 - alpha = area_tri(p0, p1) - area_tri(p2, p1) - area_rect(p0, p7); - } else { - // Case 4 - alpha = area_tri(p0, p1) - area_rect(p0, p7); - } - } else { - // Case 2 - alpha = -area_rect(p0, p7) + area_rect(p0, p3); - } - - Luma([f32::round(alpha * 255.0) as u8]) + Rgba([alphas[0], alphas[1], alphas[2], alphas[3]]) }); let output_path = matches.value_of("OUTPUT-PATH").unwrap();