From b8aafa1d789680696dbbd9536e05afea6df0a6c1 Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Sat, 30 Jan 2021 12:42:48 -0800 Subject: [PATCH] mol-gl improvements - int textures (webgl2) - read into Int32Array (webgl2) - fix ctx.parameters.maxDrawingBuffers (webgl1) - support setting frag out type (webgl2) --- src/mol-gl/renderable/util.ts | 2 +- src/mol-gl/shader-code.ts | 25 ++++++++++++++++--------- src/mol-gl/webgl/context.ts | 10 ++++++---- src/mol-gl/webgl/texture.ts | 22 ++++++++++++++++++---- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts index e9570c534..fb4caa06c 100644 --- a/src/mol-gl/renderable/util.ts +++ b/src/mol-gl/renderable/util.ts @@ -17,7 +17,7 @@ export function calculateTextureInfo (n: number, itemSize: number) { return { width, height, length: width * height * itemSize }; } -export interface TextureImage<T extends Uint8Array | Float32Array> { +export interface TextureImage<T extends Uint8Array | Float32Array | Int32Array> { readonly array: T readonly width: number readonly height: number diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index 347433a31..af1816ff0 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -23,12 +23,16 @@ export interface ShaderExtensions { readonly shaderTextureLod?: ShaderExtensionsValue } +type FragOutTypes = { [k in number]: 'vec4' | 'ivec4' } + export interface ShaderCode { readonly id: number readonly name: string readonly vert: string readonly frag: string readonly extensions: ShaderExtensions + /** Fragment shader output type only applicable for webgl2 */ + readonly outTypes: FragOutTypes } import apply_fog from './shader/chunks/apply-fog.glsl'; @@ -117,8 +121,8 @@ function addIncludes(text: string) { .replace(reMultipleLinebreaks, '\n'); } -export function ShaderCode(name: string, vert: string, frag: string, extensions: ShaderExtensions = {}): ShaderCode { - return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions }; +export function ShaderCode(name: string, vert: string, frag: string, extensions: ShaderExtensions = {}, outTypes: FragOutTypes = {}): ShaderCode { + return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions, outTypes }; } // Note: `drawBuffers` need to be 'optional' for wboit @@ -226,8 +230,6 @@ const glsl300VertPrefix = `#version 300 es `; const glsl300FragPrefixCommon = ` -layout(location = 0) out highp vec4 out_FragData0; - #define varying in #define texture2D texture #define texture2DLodEXT textureLod @@ -238,8 +240,12 @@ layout(location = 0) out highp vec4 out_FragData0; #define depthTextureSupport `; -function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExtensions, shaderExtensions: ShaderExtensions) { - const prefix = [ '#version 300 es' ]; +function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExtensions, shaderExtensions: ShaderExtensions, outTypes: FragOutTypes) { + const prefix = [ + '#version 300 es', + `layout(location = 0) out highp ${outTypes[0] || 'vec4'} out_FragData0;` + ]; + if (shaderExtensions.standardDerivatives) { prefix.push('#define enabledStandardDerivatives'); } @@ -250,7 +256,7 @@ function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExten prefix.push('#define requiredDrawBuffers'); const maxDrawBuffers = gl.getParameter(gl.MAX_DRAW_BUFFERS) as number; for (let i = 1, il = maxDrawBuffers; i < il; ++i) { - prefix.push(`layout(location = ${i}) out highp vec4 out_FragData${i};`); + prefix.push(`layout(location = ${i}) out highp ${outTypes[i] || 'vec4'} out_FragData${i};`); } } if (shaderExtensions.shaderTextureLod) { @@ -268,7 +274,7 @@ export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtens const header = getDefinesCode(defines); const vertPrefix = isWebGL2(gl) ? glsl300VertPrefix : ''; const fragPrefix = isWebGL2(gl) - ? getGlsl300FragPrefix(gl, extensions, shaders.extensions) + ? getGlsl300FragPrefix(gl, extensions, shaders.extensions, shaders.outTypes) : getGlsl100FragPrefix(extensions, shaders.extensions); const frag = isWebGL2(gl) ? transformGlsl300Frag(shaders.frag) : shaders.frag; return { @@ -276,6 +282,7 @@ export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtens name: shaders.name, vert: `${vertPrefix}${header}${shaders.vert}`, frag: `${fragPrefix}${header}${frag}`, - extensions: shaders.extensions + extensions: shaders.extensions, + outTypes: shaders.outTypes }; } \ No newline at end of file diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index be8a7098f..7fd2c6aec 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -127,12 +127,14 @@ function waitForGpuCommandsCompleteSync(gl: GLRenderingContext): void { gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel); } -export function readPixels(gl: GLRenderingContext, x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) { +export function readPixels(gl: GLRenderingContext, x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array | Int32Array) { if (isDebugMode) checkFramebufferStatus(gl); if (buffer instanceof Uint8Array) { gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer); } else if (buffer instanceof Float32Array) { gl.readPixels(x, y, width, height, gl.RGBA, gl.FLOAT, buffer); + } else if (buffer instanceof Int32Array && isWebGL2(gl)) { + gl.readPixels(x, y, width, height, gl.RGBA_INTEGER, gl.INT, buffer); } else { throw new Error('unsupported readPixels buffer type'); } @@ -204,7 +206,7 @@ export interface WebGLContext { createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32' | 'fp16', filter?: TextureFilter) => RenderTarget unbindFramebuffer: () => void - readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) => void + readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array | Int32Array) => void readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void> waitForGpuCommandsComplete: () => Promise<void> waitForGpuCommandsCompleteSync: () => void @@ -222,7 +224,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal const parameters = { maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE) as number, maxRenderbufferSize: gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) as number, - maxDrawBuffers: isWebGL2(gl) ? gl.getParameter(gl.MAX_DRAW_BUFFERS) as number : 0, + maxDrawBuffers: extensions.drawBuffers ? gl.getParameter(extensions.drawBuffers.MAX_DRAW_BUFFERS) as number : 0, maxTextureImageUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) as number, maxVertexTextureImageUnits: gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) as number, }; @@ -330,7 +332,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal }; }, unbindFramebuffer: () => unbindFramebuffer(gl), - readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) => { + readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array | Int32Array) => { readPixels(gl, x, y, width, height, buffer); }, readPixelsAsync, diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index 38b85a13a..5cad275f3 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -20,6 +20,7 @@ export type TextureKindValue = { 'image-uint8': TextureImage<Uint8Array> 'image-float32': TextureImage<Float32Array> 'image-float16': TextureImage<Float32Array> + 'image-int32': TextureImage<Int32Array> 'image-depth': TextureImage<Uint8Array> // TODO should be Uint32Array 'volume-uint8': TextureVolume<Uint8Array> 'volume-float32': TextureVolume<Float32Array> @@ -28,7 +29,7 @@ export type TextureKindValue = { } export type TextureValueType = ValueOf<TextureKindValue> export type TextureKind = keyof TextureKindValue -export type TextureType = 'ubyte' | 'ushort' | 'float' | 'fp16' +export type TextureType = 'ubyte' | 'ushort' | 'float' | 'fp16' | 'int' export type TextureFormat = 'alpha' | 'rgb' | 'rgba' | 'depth' /** Numbers are shortcuts for color attachment */ export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 @@ -43,6 +44,7 @@ export function getTarget(gl: GLRenderingContext, kind: TextureKind): number { } if (isWebGL2(gl)) { switch (kind) { + case 'image-int32': return gl.TEXTURE_2D; case 'volume-uint8': return gl.TEXTURE_3D; case 'volume-float32': return gl.TEXTURE_3D; case 'volume-float16': return gl.TEXTURE_3D; @@ -56,8 +58,12 @@ export function getFormat(gl: GLRenderingContext, format: TextureFormat, type: T case 'alpha': if (isWebGL2(gl) && type === 'float') return gl.RED; else return gl.ALPHA; - case 'rgb': return gl.RGB; - case 'rgba': return gl.RGBA; + case 'rgb': + if (isWebGL2(gl) && type === 'int') return gl.RGB_INTEGER; + return gl.RGB; + case 'rgba': + if (isWebGL2(gl) && type === 'int') return gl.RGBA_INTEGER; + return gl.RGBA; case 'depth': return gl.DEPTH_COMPONENT; } } @@ -70,18 +76,21 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat, case 'ubyte': return gl.ALPHA; case 'float': return gl.R32F; case 'fp16': return gl.R16F; + case 'int': return gl.R32I; } case 'rgb': switch (type) { case 'ubyte': return gl.RGB; case 'float': return gl.RGB32F; case 'fp16': return gl.RGB16F; + case 'int': return gl.RGB32I; } case 'rgba': switch (type) { case 'ubyte': return gl.RGBA; case 'float': return gl.RGBA32F; case 'fp16': return gl.RGBA16F; + case 'int': return gl.RGBA32I; } case 'depth': return gl.DEPTH_COMPONENT16; @@ -110,6 +119,7 @@ function getTypeSize(type: TextureType): number { case 'ushort': return 2; case 'float': return 4; case 'fp16': return 2; + case 'int': return 4; } } @@ -121,6 +131,9 @@ export function getType(gl: GLRenderingContext, extensions: WebGLExtensions, typ case 'fp16': if (extensions.textureHalfFloat) return extensions.textureHalfFloat.HALF_FLOAT; else throw new Error('extension "texture_half_float" unavailable'); + case 'int': + if (isWebGL2(gl)) return gl.INT; + else throw new Error('texture type "int" requires webgl2'); } } @@ -214,6 +227,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension (kind.endsWith('float32') && _type !== 'float') || (kind.endsWith('float16') && _type !== 'fp16') || (kind.endsWith('uint8') && _type !== 'ubyte') || + (kind.endsWith('int32') && _type !== 'int') || (kind.endsWith('depth') && _type !== 'ushort') ) { throw new Error(`texture kind '${kind}' and type '${_type}' are incompatible`); @@ -295,7 +309,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension if (layer === undefined) throw new Error('need `layer` to attach 3D texture'); gl.framebufferTextureLayer(gl.FRAMEBUFFER, getAttachment(gl, extensions, attachment), texture, 0, layer); } else { - throw new Error('unknown texture target'); + throw new Error('unknown/unsupported texture target'); } } -- GitLab