Enable alpha blending for ID pass (https://github.com/enso-org/ide/pull/364)

* Enable working alpha blending for ID pass via bit encoding of IDs in a RGB8 texture..
* Add facilities to change texture parameters.

Original commit: b46012e3fa
This commit is contained in:
Michael Mauderer 2020-04-24 13:12:40 +02:00 committed by GitHub
parent c1a399804f
commit 3e2c8bef84
17 changed files with 345 additions and 43 deletions

View File

@ -109,7 +109,8 @@ impl ComposerPass {
let name = format!("pass_{}",output.name());
let args = (self.width,self.height);
let texture = uniform::get_or_add_gpu_texture_dyn
(&self.context,&self.variables,&name,output.internal_format,output.item_type,args);
(&self.context,&self.variables,&name,output.internal_format,output.item_type,args,
Some(output.texture_parameters));
self.add_output(texture);
}
}

View File

@ -31,16 +31,21 @@ impl SymbolsRenderPass {
impl RenderPass for SymbolsRenderPass {
fn outputs(&self) -> Vec<RenderPassOutput> {
vec![ RenderPassOutput::new("color",texture::Rgba,texture::item_type::u8)
, RenderPassOutput::new("id",texture::Rgba32ui,texture::item_type::u32)
let color_parameters = texture::Parameters::default();
let id_parameters = texture::Parameters {
min_filter : texture::MinFilter::Nearest,
mag_filter : texture::MagFilter::Nearest,
..default()
};
vec![ RenderPassOutput::new("color",texture::Rgba,texture::item_type::u8,color_parameters)
, RenderPassOutput::new("id",texture::Rgba,texture::item_type::u8,id_parameters)
]
}
fn run(&mut self, context:&Context, _:&UniformScope) {
let arr = vec![0.0,0.0,0.0,0.0];
let arr2 = vec![0,0,0,0];
context.clear_bufferfv_with_f32_array(Context::COLOR,0,&arr);
context.clear_bufferuiv_with_u32_array(Context::COLOR,1,&arr2);
context.clear_bufferfv_with_f32_array(Context::COLOR,1,&arr);
self.target.set_camera(&self.views.main.camera);
self.target.render_by_ids(&self.views.main.symbols());
}

View File

@ -3,7 +3,7 @@
use crate::prelude::*;
use crate::system::gpu::types::*;
use crate::system::gpu::texture::*;
use crate::system::gpu::texture;
@ -53,19 +53,21 @@ pub struct RenderPassOutput {
/// Name of the pass.
pub name : String,
/// Internal texture format of the pass framebuffer's attachment.
pub internal_format : AnyInternalFormat,
pub internal_format : texture::AnyInternalFormat,
/// Item texture type of the pass framebuffer's attachment.
pub item_type : AnyItemType,
pub item_type : texture::AnyItemType,
/// Texture parameters that will be set when binding the texture.
pub texture_parameters : texture::Parameters,
}
impl RenderPassOutput {
/// Constructor.
pub fn new<Name:Str,F:Into<AnyInternalFormat>,T:Into<AnyItemType>>
(name:Name, internal_format:F, item_type:T) -> Self {
pub fn new<Name:Str,F:Into<texture::AnyInternalFormat>,T:Into<texture::AnyItemType>>
(name:Name, internal_format:F, item_type:T, texture_parameters:texture::Parameters) -> Self {
let name = name.into();
let internal_format = internal_format.into();
let item_type = item_type.into();
Self {name,internal_format,item_type}
Self {name,internal_format,item_type,texture_parameters}
}
/// Getter.

View File

@ -109,6 +109,15 @@ impl {
// === Target ===
// ==============
/// Result of a Decoding operation in the Target.
#[derive(Debug,Clone,Copy,Eq,PartialEq)]
enum DecodingResult{
/// Values had to be truncated.
Truncated(u8,u8,u8),
/// Values have been encoded successfully.
Ok(u8,u8,u8)
}
/// Mouse target. Contains a path to an object pointed by mouse.
#[derive(Debug,Clone,Copy,Eq,PartialEq)]
pub enum Target {
@ -120,23 +129,82 @@ pub enum Target {
}
impl Target {
fn to_internal(&self) -> Vector4<u32> {
/// Encode two u32 values into three u8 values.
///
/// This is the same encoding that is used in the `fragment_runner`. This encoding is lossy and
/// can only encode values up to 12^2=4096 each
///
/// We use 12 bits from each value and pack them into the 3 output bytes like described in the
/// following diagram.
///
/// ```text
/// Input
///
/// value1 (v1) as bytes value2 (v2) as bytes
/// +-----+-----+-----+-----+ +-----+-----+-----+-----+
/// | | | | | | | | | |
/// +-----+-----+-----+-----+ +-----+-----+-----+-----+
/// 32 24 16 8 0 32 24 16 8 0 <- Bit index
///
///
/// Output
///
/// byte1 byte2 byte3
/// +-----------+ +----------------------+ +------------+
/// | v ]12..4] | | v1 ]4..0] v2 ]4..0] | | v2 ]12..4] |
/// +-----------+ +----------------------+ +------------+
///
/// Ranges use mathematical notation for inclusion/exclusion.
/// ```
fn encode(value1:u32, value2:u32) -> DecodingResult {
let chunk1 = (value1 >> 4u32) & 0x00FFu32;
let chunk2 = (value1 & 0x000Fu32) << 4u32;
let chunk2 = chunk2 | ((value2 & 0x0F00u32) >> 8u32);
let chunk3 = value2 & 0x00FFu32;
if value1 > 2u32.pow(12) ||value2 > 2u32.pow(12) {
DecodingResult::Truncated(chunk1 as u8, chunk2 as u8, chunk3 as u8)
}else{
DecodingResult::Ok(chunk1 as u8, chunk2 as u8, chunk3 as u8)
}
}
/// Decode the symbol_id and instance_id that was encoded in the `fragment_runner`.
///
/// See the `encode` method for more information on the encoding.
fn decode(chunk1:u32, chunk2:u32, chunk3:u32) -> (u32, u32) {
let value1 = (chunk1 << 4) + (chunk2 >> 4);
let value2 = chunk3 + ((chunk2 & 0x000F) << 8);
(value1, value2)
}
fn to_internal(&self, logger:&Logger) -> Vector4<u32> {
match self {
Self::Background => Vector4::new(0,0,0,0),
Self::Symbol {symbol_id,instance_id} => Vector4::new(*symbol_id,*instance_id,0,1),
Self::Symbol {symbol_id,instance_id} => {
match Self::encode(*symbol_id,*instance_id) {
DecodingResult::Truncated(pack0,pack1,pack2) => {
warning!(logger,"Target values too big to encode: \
({symbol_id},{instance_id}).");
Vector4::new(pack0.into(),pack1.into(),pack2.into(),1)
},
DecodingResult::Ok(pack0,pack1,pack2) => {
Vector4::new(pack0.into(),pack1.into(),pack2.into(),1)
},
}
},
}
}
fn from_internal(v:Vector4<u32>) -> Self {
if v.z != 0 {
panic!("Wrong internal format for mouse target.")
}
if v.w == 0 {
Self::Background
}
else if v.w == 1 {
let symbol_id = v.x;
let instance_id = v.y;
else if v.w == 255 {
let decoded = Self::decode(v.x,v.y,v.z);
let symbol_id = decoded.0;
let instance_id = decoded.1;
Self::Symbol {symbol_id,instance_id}
} else {
panic!("Wrong internal format alpha for mouse target.")
@ -151,6 +219,58 @@ impl Default for Target {
}
// === Target Tests ===
#[cfg(test)]
mod target_tests {
use super::*;
/// Asserts that decoding encoded the given values returns the correct initial values again.
/// That means that `decode(encode(value1,value2)) == (value1,value2)`.
fn assert_valid_roundtrip(value1:u32, value2:u32) {
let pack = Target::encode(value1,value2);
match pack {
DecodingResult::Truncated {..} => {
panic!("Values got truncated. This is an invalid test case: {}, {}", value1, value1)
},
DecodingResult::Ok(pack0,pack1,pack2) => {
let unpack = Target::decode(pack0.into(),pack1.into(),pack2.into());
assert_eq!(unpack.0,value1);
assert_eq!(unpack.1,value2);
},
}
}
#[test]
fn test_roundtrip_coding() {
assert_valid_roundtrip( 0, 0);
assert_valid_roundtrip( 0, 5);
assert_valid_roundtrip( 512, 0);
assert_valid_roundtrip(1024, 64);
assert_valid_roundtrip(1024, 999);
}
#[test]
fn test_encoding() {
let pack = Target::encode(0,0);
assert_eq!(pack,DecodingResult::Ok(0,0,0));
let pack = Target::encode(3,7);
assert_eq!(pack,DecodingResult::Ok(0,48,7));
let pack = Target::encode(3,256);
assert_eq!(pack,DecodingResult::Ok(0,49,0));
let pack = Target::encode(255,356);
assert_eq!(pack,DecodingResult::Ok(15,241,100));
let pack = Target::encode(256,356);
assert_eq!(pack,DecodingResult::Ok(16,1,100));
let pack = Target::encode(31256,0);
assert_eq!(pack,DecodingResult::Truncated(161,128,0));
}
}
// =============
// === Mouse ===
@ -176,14 +296,15 @@ pub struct Mouse {
pub target : Rc<Cell<Target>>,
pub handles : Rc<Vec<callback::Handle>>,
pub frp : enso_frp::io::Mouse,
pub logger : Logger
}
impl Mouse {
pub fn new(shape:&web::dom::Shape, variables:&UniformScope) -> Self {
pub fn new(shape:&web::dom::Shape, variables:&UniformScope, logger:Logger) -> Self {
let target = Target::default();
let position = variables.add_or_panic("mouse_position",Vector2::new(0,0));
let hover_ids = variables.add_or_panic("mouse_hover_ids",target.to_internal());
let hover_ids = variables.add_or_panic("mouse_hover_ids",target.to_internal(&logger));
let button0_pressed = variables.add_or_panic("mouse_button0_pressed",false);
let button1_pressed = variables.add_or_panic("mouse_button1_pressed",false);
let button2_pressed = variables.add_or_panic("mouse_button2_pressed",false);
@ -255,7 +376,7 @@ impl Mouse {
}).forget();
Self {mouse_manager,position,hover_ids,button0_pressed,button1_pressed,button2_pressed
,button3_pressed,button4_pressed,target,handles,frp}
,button3_pressed,button4_pressed,target,handles,frp,logger}
}
}
@ -619,7 +740,8 @@ impl SceneData {
let symbols_dirty = dirty_flag;
let views = Views::mk(&logger,width,height);
let stats = stats.clone();
let mouse = Mouse::new(&dom.shape(),&variables);
let mouse_logger = logger.sub("mouse");
let mouse = Mouse::new(&dom.shape(),&variables,mouse_logger);
let shapes = ShapeRegistry::default();
let uniforms = Uniforms::new(&variables);
let dirty = Dirty {symbols:symbols_dirty,shape:shape_dirty};

View File

@ -4,13 +4,28 @@ Env env = Env(1);
vec2 position = input_local.xy ;
Shape shape = run(env,position);
float alpha = shape.color.color.raw.a;
uint alpha_no_aa = alpha > 0.5 ? uint(1) : uint(0);
output_id = uvec4(input_symbol_id,input_instance_id,0,alpha_no_aa);
// ===========================
// === Object ID Rendering ===
// ===========================
uvec3 chunks = encode(input_symbol_id,input_instance_id);
float alpha_no_aa = alpha > 0.5 ? 1.0 : 0.0;
output_id = vec4(as_float_u8(chunks.x),as_float_u8(chunks.y),as_float_u8(chunks.z),alpha_no_aa);
output_id.r *= alpha_no_aa;
output_id.g *= alpha_no_aa;
output_id.b *= alpha_no_aa;
// =======================
// === Color Rendering ===
// =======================
if (input_display_mode == 0) {
output_color = srgba(unpremultiply(shape.color)).raw;
output_color.rgb *= alpha;

View File

@ -178,3 +178,22 @@ float mul(float a, float b) {
float neg(float a) {
return -a;
}
// === Encode ===
// This encoding must correspond to the decoding in the `Target` struct in
// src\rust\ensogl\src\display\scene.rs See there for more explanation.
uvec3 encode(int value1, int value2) {
uint chunk1 = (uint(value1) >> 4u) & 0x00FFu;
uint chunk2 = (uint(value1) & 0x000Fu) << 4u;
chunk2 = chunk2 + ((uint(value2) & 0x0F00u) >> 8u);
uint chunk3 = uint(value2) & 0x00FFu;
return uvec3(chunk1,chunk2,chunk3);
}
// Encodes a uint values so it can be stored in a u8 encoded float. Will clamp values that are
// out of range.
float as_float_u8(uint value) {
return clamp(float(value) / 255.0);
}

View File

@ -58,7 +58,7 @@ impl ShapeSystem {
material.add_input ("time" , 0.0);
material.add_input ("symbol_id" , 0);
material.add_input ("display_mode" , 0);
material.add_output ("id" , Vector4::<u32>::new(0,0,0,0));
material.add_output ("id" , Vector4::<f32>::zero());
material
}

View File

@ -258,7 +258,7 @@ impl GlyphSystem {
// FIXME outputs as the number of attachments to framebuffer. We should manage this more
// FIXME intelligent. For example, we could allow defining output shader fragments,
// FIXME which will be enabled only if pass of given attachment type was enabled.
material.add_output("id", Vector4::<u32>::new(0,0,0,0));
material.add_output("id", Vector4::<f32>::new(0.0,0.0,0.0,0.0));
let code = CodeTemplate::new(BEFORE_MAIN.to_string(),MAIN.to_string(),"".to_string());
material.set_code(code);

View File

@ -348,6 +348,18 @@ impl Symbol {
let count = self.surface.point_scope().size() as i32;
let instance_count = self.surface.instance_scope().size() as i32;
// Check if we are ready to render. If we don't assert here we wil only get a warning
// that won't tell us where things went wrong.
{
let framebuffer_status = context.check_framebuffer_status(Context::FRAMEBUFFER);
debug_assert_eq!(
framebuffer_status,
Context::FRAMEBUFFER_COMPLETE,
"Framebuffer incomplete (status: {}).",
framebuffer_status
)
}
self.stats.inc_draw_call_count();
if instance_count > 0 {
self.context.draw_arrays_instanced(mode,first,count,instance_count);

View File

@ -314,7 +314,7 @@ impl SpriteSystem {
// FIXME outputs as the number of attachments to framebuffer. We should manage this more
// FIXME intelligent. For example, we could allow defining output shader fragments,
// FIXME which will be enabled only if pass of given attachment type was enabled.
material.add_output ("id", Vector4::<u32>::new(0,0,0,0));
material.add_output ("id", Vector4::<f32>::new(0.0,0.0,0.0,0.0));
material.set_main("output_color = vec4(0.0,0.0,0.0,1.0); output_id=uvec4(0,0,0,0);");
material
}

View File

@ -142,9 +142,9 @@ impl World {
fn init_composer(&self) {
let mouse_hover_ids = self.scene.mouse.hover_ids.clone_ref();
let mut pixel_read_pass = PixelReadPass::<u32>::new(&self.scene.mouse.position);
let mut pixel_read_pass = PixelReadPass::<u8>::new(&self.scene.mouse.position);
pixel_read_pass.set_callback(move |v| {
mouse_hover_ids.set(Vector4::from_iterator(v))
mouse_hover_ids.set(Vector4::from_iterator(v.iter().map(|value| *value as u32)))
});
// TODO: We may want to enable it on weak hardware.
// pixel_read_pass.set_threshold(1);

View File

@ -45,6 +45,106 @@ impl Drop for TextureBindGuard {
// ==================
// === Parameters ===
// ==================
/// Helper struct to specify texture parameters that need to be set when binding a texture.
///
/// The essential parameters that need to be set are about how the texture will be samples, i.e.,
/// how the values of the texture are interpolated at various resolutions, and how out of bounds
/// samples are handled.
///
/// For more background see: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texParameter
///
#[derive(Copy,Clone,Debug,Default)]
pub struct Parameters {
/// Specifies the setting for the texture magnification filter (`Context::TEXTURE_MIN_FILTER`).
pub min_filter : MinFilter,
/// Specifies the setting for the texture minification filter (`Context::TEXTURE_MAG_FILTER`).
pub mag_filter : MagFilter,
/// Specifies the setting for the wrapping function for texture coordinate s
/// (`Context::TEXTURE_WRAP_S`).
pub wrap_s : Wrap,
/// Specifies the setting for the wrapping function for texture coordinate t
/// (`Context::TEXTURE_WRAP_T`).
pub wrap_t : Wrap,
}
impl Parameters {
/// Applies the context parameters in the given context.
pub fn apply_parameters(self, context:&Context) {
let target = Context::TEXTURE_2D;
context.tex_parameteri(target,Context::TEXTURE_MIN_FILTER,self.min_filter as i32);
context.tex_parameteri(target,Context::TEXTURE_MIN_FILTER,self.mag_filter as i32);
context.tex_parameteri(target,Context::TEXTURE_WRAP_S,self.wrap_s as i32);
context.tex_parameteri(target,Context::TEXTURE_WRAP_T,self.wrap_t as i32);
}
}
// === Parameter Types ===
/// Valid Parameters for the `gl.TEXTURE_MAG_FILTER` texture setting.
///
/// Specifies how values are interpolated if the texture is rendered at a resolution that is
/// lower than its native resolution.
#[derive(Copy,Clone,Debug)]
#[allow(missing_docs)]
pub enum MagFilter {
Linear = Context::LINEAR as isize,
Nearest = Context::NEAREST as isize,
}
// Note: The parameters implement our own default, not the WebGL one.
impl Default for MagFilter {
fn default() -> Self {
Self::Linear
}
}
/// Valid Parameters for the `gl.TEXTURE_MIN_FILTER` texture setting.
///
/// Specifies how values are interpolated if the texture is rendered at a resolution that is
/// lower than its native resolution.
#[derive(Copy,Clone,Debug)]
#[allow(missing_docs)]
pub enum MinFilter {
Linear = Context::LINEAR as isize,
Nearest = Context::NEAREST as isize,
NearestMipmapNearest = Context::NEAREST_MIPMAP_NEAREST as isize,
LinearMipmapNearest = Context::LINEAR_MIPMAP_NEAREST as isize,
NearestMipmapLinear = Context::NEAREST_MIPMAP_LINEAR as isize,
LinearMipmapLinear = Context::LINEAR_MIPMAP_LINEAR as isize,
}
// Note: The parameters implement our own default, not the WebGL one.
impl Default for MinFilter {
fn default() -> Self {
Self::Linear
}
}
/// Valid Parameters for the `gl.TEXTURE_WRAP_S` and `gl.TEXTURE_WRAP_T` texture setting.
///
/// Specifies what happens if a texture is sampled out of bounds.
#[derive(Copy,Clone,Debug)]
#[allow(missing_docs)]
pub enum Wrap {
Repeat = Context::REPEAT as isize,
ClampToEdge = Context::CLAMP_TO_EDGE as isize,
MirroredRepeat = Context::MIRRORED_REPEAT as isize,
}
// Note: The parameters implement our own default, not the WebGL one.
impl Default for Wrap {
fn default() -> Self {
Self::ClampToEdge
}
}
// ===============
// === Texture ===
// ===============
@ -58,6 +158,7 @@ where Storage: StorageRelation<InternalFormat,ItemType> {
storage : StorageOf<Storage,InternalFormat,ItemType>,
gl_texture : WebGlTexture,
context : Context,
parameters : Parameters
}
@ -125,6 +226,23 @@ where S:StorageRelation<I,T> {
pub fn storage(&self) -> &StorageOf<S,I,T> {
&self.storage
}
/// Getter.
pub fn parameters(&self) -> &Parameters {
&self.parameters
}
}
// === Setters ===
impl<S,I,T> Texture<S,I,T>
where S:StorageRelation<I,T> {
/// Setter.
pub fn set_parameters(&mut self, parameters: Parameters) {
self.parameters = parameters;
}
}
@ -161,16 +279,13 @@ where S:StorageRelation<I,T> {
let storage = storage.into();
let context = context.clone();
let gl_texture = context.create_texture().unwrap();
Self {storage,gl_texture,context}
let parameters = default();
Self {storage,gl_texture,context,parameters}
}
/// Sets the texture wrapping parameters.
pub fn set_texture_parameters(context:&Context) {
let target = Context::TEXTURE_2D;
let wrap = Context::CLAMP_TO_EDGE as i32;
context.tex_parameteri(target,Context::TEXTURE_MIN_FILTER,Context::LINEAR as i32);
context.tex_parameteri(target,Context::TEXTURE_WRAP_S,wrap);
context.tex_parameteri(target,Context::TEXTURE_WRAP_T,wrap);
/// Applies this textures' parameters in the given context.
pub fn apply_texture_parameters(&self, context:&Context) {
self.parameters.apply_parameters(context);
}
}
@ -199,7 +314,7 @@ where S : StorageRelation<I,T>,
result.unwrap();
}
Self::set_texture_parameters(&self.context);
self.apply_texture_parameters(&self.context);
}
}

View File

@ -62,7 +62,7 @@ TextureReload for Texture<GpuOnly,I,T> {
self.context().tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array
(target,level,internal_format,width,height,border,format,elem_type,None).unwrap();
Self::set_texture_parameters(self.context());
self.apply_texture_parameters(self.context());
}
}

View File

@ -82,6 +82,7 @@ TextureReload for Texture<RemoteImage,I,T> {
let image_ref_opt = image_ref.clone();
let context = self.context().clone();
let gl_texture = self.gl_texture().clone();
let parameters = *self.parameters();
let callback: Closure<dyn FnMut()> = Closure::once(move || {
let _keep_alive = callback_ref2;
let image = image_ref_opt.borrow();
@ -94,7 +95,7 @@ TextureReload for Texture<RemoteImage,I,T> {
context.tex_image_2d_with_u32_and_u32_and_html_image_element
(target,level,internal_format,format,elem_type,&image).unwrap();
Self::set_texture_parameters(&context);
parameters.apply_parameters(&context);
});
let js_callback = callback.as_ref().unchecked_ref();
let image = image_ref.borrow();

View File

@ -25,6 +25,11 @@
/// currently stored pixel. Blending applies only in RGBA mode and only if the color buffer has a
/// fixed-point or floating-point format; in color index mode or if the color buffer has an
/// integer format, it is bypassed.
///
/// The features of some texture formats can be modified through extensions. For example, the
/// the extension `EXT_color_buffer_float` can make the group of float texture (e.g., `Rgba32f`)
/// color renderable.
///
#[macro_export]
macro_rules! with_texture_format_relations { ($f:ident $args:tt) => { $crate::$f! { $args
// INTERNAL_FORMAT FORMAT SAMPLER COL FILT BLEND [POSSIBLE_TYPE:BYTES_PER_TEXTURE_ELEM]

View File

@ -334,12 +334,16 @@ macro_rules! define_get_or_add_gpu_texture_dyn {
, internal_format : AnyInternalFormat
, item_type : AnyItemType
, provider : P
, parameters : Option<Parameters>
) -> AnyTextureUniform {
let provider = provider.into();
match (internal_format,item_type) {
$((AnyInternalFormat::$internal_format, AnyItemType::$item_type) => {
let texture = Texture::<GpuOnly,$internal_format,$item_type>
let mut texture = Texture::<GpuOnly,$internal_format,$item_type>
::new(&context,provider);
if let Some(parameters) = parameters {
texture.set_parameters(parameters);
}
let uniform = scope.get_or_add(name,texture).unwrap();
uniform.into()
})*

View File

@ -204,7 +204,8 @@ pub fn get_webgl2_context(canvas:&HtmlCanvasElement) -> WebGl2RenderingContext {
let options = js_sys::Object::new();
js_sys::Reflect::set(&options, &"antialias".into(), &false.into()).unwrap();
let context = canvas.get_context_with_context_options("webgl2",&options).unwrap().unwrap();
context.dyn_into().unwrap()
let context : WebGl2RenderingContext = context.dyn_into().unwrap();
context
}
pub fn try_request_animation_frame(f:&Closure<dyn FnMut(f64)>) -> Result<i32> {