WebGL: async operations? - webgl

I'd like to know if there are any async calls for WebGL that one could take advantage of?
I have looked into Spec v1 and Spec v2 they don't mention anything. In V2, there is a WebGL Query mechanism which I don't think is what I'm looking for.
A search on the web didn't come up with anything definitive. There is this example and is not clear how the sync and async version differ. http://toji.github.io/shader-perf/
Ultimately I'd like to be able to some of all of these asynchronously:
readPixels
texSubImage2D and texImage2D
Shader compilation
program linking
draw???
There is a glFinish operation and the documentation for it says: "does not return until the effects of all previously called GL commands are complete.". To me this means that there are asynchronous operations which can be awaited for by calling Finish()?
And some posts on the web suggest that calling getError() also forces some synchronicity and is not a very desired thing to do after every call.

It depends on your definition of async.
In Chrome (Firefox might also do this now? not sure). Chrome runs all GPU code in a separate process from JavaScript. That means your commands are running asynchronous. Even OpenGL itself is designed to be asynchronous. The functions (WebGL/OpenGL) insert commands into a command buffer. Those are executed by some other thread/process. You tell OpenGL "hey, I have new commands for you to execute!" by calling gl.flush. It executes those commands asynchronously. If you don't call gl.flush it will be called for you periodically when too many commands have been issued. It will also be called when the current JavaScript event exits, assuming you called any rendering command to the canvas (gl.drawXXX, gl.clear).
In this sense everything about WebGL is async. If you don't query something (gl.getXXX, gl.readXXX) then stuff is being handled (drawn) out of sync with your JavaScript. WebGL is giving you access to a GPU after all running separately from your CPU.
Knowing this one way to take advantage of it in Chrome is to compile shaders async by submitting the shaders
for each shader
s = gl.createShader()
gl.shaderSource(...);
gl.compileShader(...);
gl.attachShader(...);
gl.linkProgram(...)
gl.flush()
The GPU process will now be compiling your shaders. So, say, 250ms later you only then start asking if it succeeded and querying locations, then if it took less then 250ms to compile and link the shaders it all happened async.
In WebGL2 there is at least one more clearly async operation, occlusion queries, in which WebGL2 can tell you how many pixels were drawn for a group of draw calls. If non were drawn then your draws were occluded. To get the answer you periodically pole to see if the answer is ready. Typically you check next frame and in fact the WebGL spec requires the answer to not be available until the next frame.
Otherwise, at the moment (August 2018), there is no explicitly async APIs.
Update
HankMoody brought up in comments that texImage2D is sync. Again, it depends on your definition of async. It takes time to add commands and their data. A command like gl.enable(gl.DEPTH_TEST) only has to add 2-8 bytes. A command like gl.texImage2D(..., width = 1024, height = 1024, RGBA, UNSIGNED_BYTE) has to add 4meg!. Once that 4meg is uploaded the rest is async, but the uploading takes time. That's the same for both commands it's just that adding 2-8 bytes takes a lot less time than adding 4meg.
To more be clear, after that 4 meg is uploaded many other things happen asynchronously. The driver is called with the 4 meg. The driver copies that 4meg. The driver schedules that 4meg to be used sometime later as it can't upload the data immediately if the texture is already in use. Either that or it does upload it immediately just to a new area and then swaps what the texture is pointing to at just before a draw call that actually uses that new data. Other drivers just copy the data and store it and wait until the texture is used in a draw call to actually update the texture. This is because texImage2D has crazy semantics where you can upload different size mips in any order so the driver can't know what's actually needed in GPU memory until draw time since it has no idea what order you're going to call texIamge2D. All of this stuff mentioned in this paragraph happens asynchronously.
But that does bring up some more info.
gl.texImage2D and related commands have to do a TON of work. One is they have to honor UNPACK_FLIP_Y_WEBGL and UNPACK_PREMULTIPLY_ALPHA_WEBGL so they man need to make a copy of multiple megs of data to flip it or premultiply it. Second, if you pass them a video, canvas, or image they may have to do heavy conversions or even reparse the image from source especially in light of UNPACK_COLORSPACE_CONVERSION_WEBGL. Whether this happens in some async like way or not is up to the browser. Since you don't have direct access to the image/video/canvas it would be possible for the browser to do all of this async but one way or another all that work has to happen.
To make much of that work ASYNC the ImageBitmap API was added. Like most Web APIs it's under-specified but the idea is you first do a fetch (which is async). You then request to create an ImageBitmap and give it options for color conversion, flipping, pre-multiplied alpha. This also happens async. You then pass the result to gl.texImage2D with the hope being that the browser was able to make do all the heavy parts before it got to this last step.
Example:
// note: mode: 'cors' is because we are loading
// from a different domain
async function main() {
const response = await fetch('https://i.imgur.com/TSiyiJv.jpg', {mode: 'cors'})
if (!response.ok) {
return console.error('response not ok?');
}
const blob = await response.blob();
const bitmap = await createImageBitmap(blob, {
premultiplyAlpha: 'none',
colorSpaceConversion: 'none',
});
const gl = document.querySelector("canvas").getContext("webgl");
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
{
const level = 0;
const internalFormat = gl.RGBA;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
format, type, bitmap);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
}
const vs = `
uniform mat4 u_worldViewProjection;
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texCoord;
void main() {
v_texCoord = texcoord;
gl_Position = u_worldViewProjection * position;
}
`;
const fs = `
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_tex;
void main() {
gl_FragColor = texture2D(u_tex, v_texCoord);
}
`;
const m4 = twgl.m4;
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
const uniforms = {
u_tex: tex,
};
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 10;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 4, -6];
const target = [0, 0, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
const world = m4.rotationY(time);
uniforms.u_worldViewProjection = m4.multiply(viewProjection, world);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Unfortunately this only works in Chrome as of 2018 August. Firefox bug is here. Other browsers I don't know.

Related

how to bind a player position to a shader with melonjs?

I've created a glsl shader as:
<script id="player-fragment-shader" type="x-shader/x-fragment">
precision highp float;
varying vec3 fNormal;
uniform vec2 resolution;
float circle(in vec2 _pos, in float _radius) {
vec2 dist = _pos - vec2(0.5);
return 1.-smoothstep(_radius - (_radius * 0.5),
_radius + (_radius * 0.5),
dot(dist, dist) * 20.0);
}
void main() {
vec2 pos = gl_FragCoord.xy/resolution.xy;
// Subtract the inverse of orange from white to get an orange glow
vec3 color = vec3(circle(pos, 0.8)) - vec3(0.0, 0.25, 0.5);
gl_FragColor = vec4(color, 0.8);
}
</script>
<script id="player-vertex-shader" type="x-shader/x-vertex">
precision highp float;
attribute vec3 position;
attribute vec3 normal;
uniform mat3 normalMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
void main() {
vec4 pos = modelViewMatrix * vec4(position, 0.25);
gl_Position = projectionMatrix * pos;
}
</script>
I initialize it in the game load by running:
var vertShader = document.getElementById("player-vertex-shader").text;
var fragShader = document.getElementById("player-fragment-shader").text;
var shader = me.video.shader.createShader(me.video.renderer.compositor.gl, vertShader, fragShader);
This is done after video is initialized, and seems to compile the shader program and load fine. The shader also seems to work fine when loading it up in shaderfrog.com and other similar sites.
The problem is, it's leaving me with a totally black screen until I move the character and it redraws. I've read over the webgl fundamentals site, and it seems what I'm missing is binding the character position to the GL buffer.
How do I do this in melonjs.
Hi, I wrote the original WebGL compositor for melonJS.
tl;dr: Force the frame to redraw by returning true from your character's entity.update() method. (Or alternatively, increase the animation frame rate to match the game frame rate.)
Example overriding the update method:
update: function (dt) {
this._super(me.Entity, "update", [dt]);
return true;
}
This allows the update to continue operating normally (e.g. updating animation state, etc.) but returning true to force the frame to redraw every time.
It might help to understand how the compositor works, and how your shader is interacting with melonJS entities. This describes the inner workings of WebGL integration with melonJS. In short, there is no explicit step to bind positions to the shader. Positions are sent via the vertex attribute buffer, which is batched up (usually for an entire frame) and sent as one big array to WebGL.
The default compositor can be replaced if you need more control over building the vertex buffer, or if you want to do other custom rendering passes. This is done by passing a class reference to me.video.init in the options.compositor argument. The default is me.WebGLRenderer.Compositor:
me.video.init(width, height, {
wrapper: "screen",
renderer : me.video.WEBGL,
compositor: me.WebGLRenderer.Compositor
});
During the draw loop, the default compositor adds a new quad element to the vertex attribute array buffer for every me.WebGLRenderer.drawImage call. This method emulates the DOM canvas method of the same name. The implementation is very simple; it just converts the arguments into a quad and calls the compositor's addQuad method. This is where the vertex attribute buffer is actually populated.
After the vertex attribute buffer has been completed, the flush method is called, which sends the vertex buffer to the GPU with gl.drawElements.
melonJS takes drawing optimization to the extreme. Not only does it batch like-renderables to reduce the number of draw calls (as described above) but it also doesn't send any draw calls if there is nothing to draw. This condition occurs when the frame is identical to the last frame drawn. For example, no entity has moved, the viewport has not scrolled, idle animations have not advanced to the next state, on-screen timer has not elapsed a full second, etc.
It is possible to force the frame to redraw by having any entity in the scene return true from its update method. This is a signal to the game engine that the frame needs to be redrawn. The process is described in more detail on the wiki.

How to implement VBO double buffering in WebGL?

I want to increase performance in my WebGL project by setting up VBO double buffering. Although there are plenty of articles on this topic, I failed to find one with a coding example.
I tried the following:
// Initial setup...
var buff1 = gl.createBuffer();
var buff2 = gl.createBuffer();
var buffActive = buff1;
// For each frame, change the verticies data and then ...
gl.bindBuffer(gl.ARRAY_BUFFER, buffActive);
gl.bufferData(gl.ARRAY_BUFFER, vertArray, gl.DYNAMIC_DRAW);
gl.drawArrays(gl.TRIANGLES, start, count);
buffActive = (buffActive === buff2) ? buff1 : buff2;
The calls to gl.bindBuffer and gl.bufferData work fine. However, gl.drawArrays always renders using only the data from buff1. I assumed gl.drawArrays would render using whichever VBO is currently bound to gl.ARRAY_BUFFER, but apparently that's not the case.
Does anyone see what I'm missing?
The issue here is you have bound both buff1 and buff2 to the same buffer, vertArray.
A better thing to do is create two vertex arrays (say, vertArray1 and vertArray2) and
to bind each vertex array to a separate buffer object during the initial setup (and not each frame, because
bufferData() destroys and reinitializes a buffer object's data, which may be an
expensive operation if doing so requires uploading the data to the GPU).
Here's an example:
// Initial setup
var buff1 = gl.createBuffer();
var buff2 = gl.createBuffer();
// Associate the buffer data once during initial setup.
// Note that we use two vertex arrays, vertArray1 and vertArray2,
// rather than one
gl.bindBuffer(gl.ARRAY_BUFFER, buff1);
gl.bufferData(gl.ARRAY_BUFFER, vertArray1, gl.DYNAMIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, buff2);
gl.bufferData(gl.ARRAY_BUFFER, vertArray2, gl.DYNAMIC_DRAW);
var buffActive = buff1;
var vertArrayActive = vertArray1;
// For each frame:
gl.bindBuffer(gl.ARRAY_BUFFER, buffActive);
// No need to re-load the buffer data
gl.drawArrays(gl.TRIANGLES, start, count);
buffActive = (buffActive === buff2) ? Buff1 : buff2;
vertArrayActive = (vertArrayActive === vertArray2) ? VertArray1 : vertArray2;
If you implement VBO double buffering then you need to ensure calls to gl.vertexAttribPointer and gl.enableVertexAttribArray for vertex attribute data (stored as interleaved data in vertArray) are made every time the VBOs switch, rather than just during setup. Otherwise, your vertex attribute pointers will always be referencing data in the first VBO, and that VBO will now only be updated every other frame as a consequence of double buffering. The calls to gl.vertexAttribPointer and gl.enableVertexAttribArray must now be made after switching VBOs and before gl.drawArrays.

WebGL - rendering a buffer from memory

I'm a newbie with webgl, is it possible to read a buffer from host memory (RAM) and render it?
Or perhaps to read a buffer from a CUDA buffer and render it. I can do the first in openGL but I'm wondering if I can do that in webGL
Edit: I'm trying to be more precise in what I want to do..
I have a RGBA buffer with an image which might immediately be rendered on a GL context with glDrawPixels. This was produced by a CUDA code (so the buffer was originally on the gpu memory). Is there any way to visualize this into a webGL window?
If you mean some JavaScript buffer by "host memory", then yes: You upload that into a WebGL buffer object and do whatever you want with it then. There's no CUDA interaction for WebGL. And you can't just "grab" any other process's memory for your things. Memory protection is a good thing.
Please be a bit more specific at what you want to do.
Update due to comment.
You think you may have a RGBA buffer, but you don't. Some other process has or had this buffer, but as far as WebGL is concerned, this buffer doesn't exist. It's part of a different process, which memory (and the system makes no difference between CPU and GPU memory there) is (or at least should be) protected from the access of other processes.
Think about it: If your browser (via WebGL) could access random stuff that resides in part of your system's memory that don't belong to it, this would hugely impair security. When it comes to regular OpenGL you are actually able to look into the uninitialized parts of memory to see what was there before. But for WebGL, being an Internet technology, a lot of checking is done, that you can't reach outside your sandbox. Heck, there were even new extensions introduced to make OpenGL more robust against data exfiltration and GPU based attack vectors.
It goes even so far, that in WebGL not only the memory is protected between processes, but also between WebGL instances, which may run in the same process.
Here's an excerpt from http://omino.com/experiments/webgl/OmGl.js which is the meat of a "drawtexture" method that renders a JavaScript array onto a webgl canvas.
A simple instance of its use it at http://omino.com/experiments/webgl/manualTexture.html. (It references some helpers in OmGl.js; hopefully clear enough, though.)
Hope that helps!
var _omglDrawTextureProgGl = null;
var _omglDrawTextureProg = null;
var _omglPosBuffer = null;
/*
* fill the current frame buffer with the texture.
* Right up to the edges.
* you can specify which texture unit, if you like.
* reduce thrashing maybe.
*
* pass in your own
*/
OmGl.drawTexture = function drawTexture(gl,texture,textureUnit,prog)
{
if(!textureUnit)
textureUnit = 0;
if(!prog)
{
if(gl != _omglDrawTextureProgGl || !_omglDrawTextureProg)
{
var dtvs = "attribute vec2 pos;"
+ "varying vec2 unitPos;"
+ "void main() {"
+ " unitPos = (pos.xy + 1.0) / 2.0;"
+ " gl_Position = vec4(pos,0,1);"
+ "}";
var dtfs = "precision mediump float;"
+ "uniform sampler2D uSampler;"
+ "varying vec2 unitPos;"
+ "void main() {"
+ " gl_FragColor = texture2D(uSampler, unitPos);"
+ "}";
_omglDrawTextureProg = OmGl.linkShaderProgram(gl,dtvs,dtfs);
_omglDrawTextureProgGl = gl;
}
prog = _omglDrawTextureProg;
}
gl.useProgram(prog);
// Two triangles which fill the entire canvas.
var posPoints = [
-1,-1,
1,-1,
1,1,
-1,-1,
1,1,
-1,1
];
gl.activeTexture(gl.TEXTURE0 + textureUnit);
gl.bindTexture(gl.TEXTURE_2D, texture);
var z = gl.getUniformLocation(prog, "uSampler");
gl.uniform1i(z, textureUnit);
_omglPosBuffer = OmGl.setAttributeFloats(gl, prog, "pos", 2, posPoints, _omglPosBuffer);
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, posPoints.length / 2);
}
(edit -- updated the code at http://omino.com/experiments/webgl/manualTexture.html to be much cleaner.)

Is it important to call glDisableVertexAttribArray()?

I'm not entirely clear on the scope of enabling vertex attrib arrays. I've got several different shader programs with differing numbers of vertex attributes. Are glEnableVertexAttribArray calls local to a shader program, or global?
Right now I'm enabling vertex attrib arrays when I create the shader program, and never disabling them, and all seems to work, but it seems like I'm possibly supposed to enable/disable them right before/after draw calls. Is there an impact to this?
(I'm in WebGL, as it happens, so we're really talking about gl.enableVertexAttribArray and gl.disableVertexAttribArray. I'll note also that the orange book, OpenGL Shading Language, is quite uninformative about these calls.)
The state of which Vertex Attribute Arrays are enabled can be either bound to a Vertex Array Object (VAO), or be global.
If you're using VAOs, then you should not disable attribute arrays, as they are encapsulated in the VAO.
However for global vertex attribute array enabled state you should disable them, because if they're left enabled OpenGL will try to read from arrays, which may be bound to a invalid pointer, which may either crash your program if the pointer is to client address space, or raise a OpenGL error if it points out of the limits of a bound Vertex Buffer Object.
WebGL is not the same as OpenGL.
In WebGL leaving arrays enabled is explicitly allowed as long as there is a buffer attached to the attribute and (a) if it's used it's large enough to satisfy the draw call or (b) it's not used.
Unlike OpenGL ES 2.0, WebGL doesn't allow client side arrays.
Proof:
const gl = document.querySelector("canvas").getContext("webgl");
const vsUses2Attributes = `
attribute vec4 position;
attribute vec4 color;
varying vec4 v_color;
void main() {
gl_Position = position;
gl_PointSize = 20.0;
v_color = color;
}
`;
const vsUses1Attribute = `
attribute vec4 position;
varying vec4 v_color;
void main() {
gl_Position = position;
gl_PointSize = 20.0;
v_color = vec4(0,1,1,1);
}
`
const fs = `
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`;
const program2Attribs = twgl.createProgram(gl, [vsUses2Attributes, fs]);
const program1Attrib = twgl.createProgram(gl, [vsUses1Attribute, fs]);
function createBuffer(data) {
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
return buf;
}
const buffer3Points = createBuffer([
-0.7, 0.5,
0.0, 0.5,
0.7, 0.5,
]);
const buffer3Colors = createBuffer([
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
]);
const buffer9Points = createBuffer([
-0.8, -0.5,
-0.6, -0.5,
-0.4, -0.5,
-0.2, -0.5,
0.0, -0.5,
0.2, -0.5,
0.4, -0.5,
0.6, -0.5,
0.8, -0.5,
]);
// set up 2 attributes
{
const posLoc = gl.getAttribLocation(program2Attribs, 'position');
gl.enableVertexAttribArray(posLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer3Points);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
const colorLoc = gl.getAttribLocation(program2Attribs, 'color');
gl.enableVertexAttribArray(colorLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer3Colors);
gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
}
// draw
gl.useProgram(program2Attribs);
gl.drawArrays(gl.POINTS, 0, 3);
// setup 1 attribute (don't disable the second attribute
{
const posLoc = gl.getAttribLocation(program1Attrib, 'position');
gl.enableVertexAttribArray(posLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer9Points);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
}
// draw
gl.useProgram(program1Attrib);
gl.drawArrays(gl.POINTS, 0, 9);
const err = gl.getError();
console.log(err ? `ERROR: ${twgl.glEnumToString(gl, err)}` : 'no WebGL errors');
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<p>
1st it draws 3 points (3 vertices, 2 attributes)<br/>
2nd it draws 9 points (9 vertices, 1 attribute)<br/>
It does NOT call gl.disableVertexAttrib so on the second draw call one of the attributes is still enabled. It is pointing to a buffer with only 3 vertices in it even though 9 vertices will be drawn. There are no errors.
</p>
<canvas></canvas>
Another example, just enable all the attributes, then draw with a shader that uses no attributes (no error) and also draw with a shader that uses 1 attribute (again no error), no need to call gl.disbleVertexAttribArray
const gl = document.querySelector("canvas").getContext("webgl");
const vsUses1Attributes = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 20.0;
}
`;
const vsUses0Attributes = `
void main() {
gl_Position = vec4(0, 0, 0, 1);
gl_PointSize = 20.0;
}
`
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
`;
const program0Attribs = twgl.createProgram(gl, [vsUses0Attributes, fs]);
const program1Attrib = twgl.createProgram(gl, [vsUses1Attributes, fs]);
function createBuffer(data) {
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
return buf;
}
const buffer3Points = createBuffer([
-0.7, 0.5,
0.0, 0.5,
0.7, 0.5,
]);
const buffer0Points = createBuffer([]);
// enable all the attributes and bind a buffer to them
const maxAttrib = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
for (let i = 0; i < maxAttrib; ++i) {
gl.enableVertexAttribArray(i);
gl.vertexAttribPointer(i, 2, gl.FLOAT, false, 0, 0);
}
gl.useProgram(program0Attribs);
gl.drawArrays(gl.POINTS, 0, 1);
gl.useProgram(program1Attrib);
const posLoc = gl.getAttribLocation(program1Attrib, 'position');
gl.bindBuffer(gl.ARRAY_BUFFER, buffer3Points);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.POINTS, 0, 3);
const err = gl.getError();
console.log(err ? `ERROR: ${twgl.glEnumToString(gl, err)}` : 'no WebGL errors');
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<p>
1st it enables all attributes<br/>
2nd it draws 1 point that needs no attributes (no error)<br/>
3rd it draws 3 points that need 1 attribute (no error)<br/>
It does NOT call gl.disableVertexAttribArray on any of the attributes so they are all still enabled. There are no errors.
</p>
<canvas></canvas>
For webGL I'm going to go with yes, it is important to call gl.disableVertexAttribArray.
Chrome was giving me this warning:
WebGL: INVALID_OPERATION: drawElements: attribs not setup correctly
This was happening when the program changed to one using less than the maximum number of attributes. Obviously the solution was to disable the unused attributes before drawing.
If all your programs use the same number of attributes, you may well get away with calling gl.enableVertexAttribArray once on initialization. Otherwise you'll need to manage them when you change programs.
Think of it as attributes are local to a VAO and not a shader program. the VBOs are in GPU memory.
Now consider that, in WebGL, there is a default VAO that WebGL uses by default. (it can also be a VAO created by the programmer, the same concept applies). This VAO contains a target called ARRAY_BUFFER to which any VBO in the GPU memory can bound. This VAO also contains and attribute array with a fixed number of attribute slots (number depends on implementation and platform, here lets say 8 which is the minimum required by the Webgl specification). Also, this VAO will have ELEMENT_ARRAY_BUFFER target to which any index data buffer can be bound.
Now, when you create a shader program, it will have the attributes you specify. Webgl will assign one of the possible attribute slot "numbers" to all of the attributes specified in the program when you link the shader program. now attributes will use the corresponding attribute slots in the VAO to access the data bound to the ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER targets in the VAO. Now, when you use functions gl.enableVertexAttribArray(location) and gl.vertexAttribPointer(location,....) you are not changing any characteristics of the attributes in the shader program (they simply have a attribute number which refers to one of the attribute slots in the VAO that they will use to access data). What you are actually doing is modifying the state of the attribute slot in the VAO using its location number. SO then for the attributes in the program to be able to access the data, its corresponding attribute slot in the VAO must be enabled (gl.enableVertexAttribArray()). And we have to configure the attribute slot so it can read the data from the buffer bound to the ARRAY_BUFFER correctly (gl.vertexAttribPointer()). one a VBO is set for a slot it wont change, even if we unbind it from the target the attribute slot csn still red from the VBO as long as it is there in GPU memory. Also, there must be some buffer bound to the targets of the VAO (gl.bindBuffer()). So gl.enableVertexAttribArray(location) will enable the attribute slot specified by 'location' in the current VAO. gl.disableVertexAttribArray(location) will disable it. It has nothing to do with the shader program though. Even if you uses a different shader program, these attribute slots's state wont be affected.
So, if two different shader programs use the same attribute slots, there wont be any error because the corresponding attribute slots in the VAO is already active. but the data from the targets might be read incorrectly if the attributes are required interpret the data differently in the two shader programs. Now consider, if the two shader programs use different attribute slots, then you might enable the second shaders program's required attribute slots and think that your program should work. But the already enabled attribute slots (which were enabled by the previous shader program) will still be enabled but wont be used. This causes an error.
So when changing shader programs, we must ensure that the enabled attribute slots in the VAO that wont be used by this shader program must be disabled. Although we might now specify any VAOs explicitly, Webgl works like this by default.
One way is to maintain a list of enabled attributes on the javascript side and disable all enabled attribute slots when switching program while still using the same VAO. Another way to deal with this problem is to create custom VAOs that is accessed by one shader program only. but it is less efficient. Yet another way is to binding attribute locations to fixed slots before the shader program is linked using gl.bindAttribLocation().

Efficient way to render a bunch of layered textures?

What's the efficient way to render a bunch of layered textures? I have some semitransparent textured rectangles that I position randomly in 3D space and render them from back to front.
Currently I call d3dContext->PSSetShaderResources() to feed the pixel shader with a new texture before each call to d3dContext->DrawIndexed(). I have a feeling that I am copying the texture to the GPU memory before each draw. I might have 10-30 ARGB textures roughly 1024x1024 pixels each and they are associated across 100-200 rectangles that I render on screen. My FPS is OK at 100, but goes pretty bad around 200. I possibly have some inefficiencies elsewhere since this is my first semi-serious D3D code, but I strongly suspect this has to do with copying the textures back and forth. 30*1024*1024*4 is 120MB, which is a bit high for a Metro Style App that should target any Windows 8 device. So putting them all in there might be a stretch, but maybe I could at least cache a few somehow? Any ideas?
*EDIT - Some code snippets added
Constant Buffer
struct ModelViewProjectionConstantBuffer
{
DirectX::XMMATRIX model;
DirectX::XMMATRIX view;
DirectX::XMMATRIX projection;
float opacity;
float3 highlight;
float3 shadow;
float textureTransitionAmount;
};
The Render Method
void RectangleRenderer::Render()
{
// Clear background and depth stencil
const float backgroundColorRGBA[] = { 0.35f, 0.35f, 0.85f, 1.000f };
m_d3dContext->ClearRenderTargetView(
m_renderTargetView.Get(),
backgroundColorRGBA
);
m_d3dContext->ClearDepthStencilView(
m_depthStencilView.Get(),
D3D11_CLEAR_DEPTH,
1.0f,
0
);
// Don't draw anything else until all textures are loaded
if (!m_loadingComplete)
return;
m_d3dContext->OMSetRenderTargets(
1,
m_renderTargetView.GetAddressOf(),
m_depthStencilView.Get()
);
UINT stride = sizeof(BasicVertex);
UINT offset = 0;
// The vertext buffer only has 4 vertices of a rectangle
m_d3dContext->IASetVertexBuffers(
0,
1,
m_vertexBuffer.GetAddressOf(),
&stride,
&offset
);
// The index buffer only has 4 vertices
m_d3dContext->IASetIndexBuffer(
m_indexBuffer.Get(),
DXGI_FORMAT_R16_UINT,
0
);
m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_d3dContext->IASetInputLayout(m_inputLayout.Get());
FLOAT blendFactors[4] = { 0, };
m_d3dContext->OMSetBlendState(m_blendState.Get(), blendFactors, 0xffffffff);
m_d3dContext->VSSetShader(
m_vertexShader.Get(),
nullptr,
0
);
m_d3dContext->PSSetShader(
m_pixelShader.Get(),
nullptr,
0
);
m_d3dContext->PSSetSamplers(
0, // starting at the first sampler slot
1, // set one sampler binding
m_sampler.GetAddressOf()
);
// number of rectangles is in the 100-200 range
for (int i = 0; i < m_rectangles.size(); i++)
{
// start rendering from the farthest rectangle
int j = (i + m_farthestRectangle) % m_rectangles.size();
m_vsConstantBufferData.model = m_rectangles[j].transform;
m_vsConstantBufferData.opacity = m_rectangles[j].Opacity;
m_vsConstantBufferData.highlight = m_rectangles[j].Highlight;
m_vsConstantBufferData.shadow = m_rectangles[j].Shadow;
m_vsConstantBufferData.textureTransitionAmount = m_rectangles[j].textureTransitionAmount;
m_d3dContext->UpdateSubresource(
m_vsConstantBuffer.Get(),
0,
NULL,
&m_vsConstantBufferData,
0,
0
);
m_d3dContext->VSSetConstantBuffers(
0,
1,
m_vsConstantBuffer.GetAddressOf()
);
m_d3dContext->PSSetConstantBuffers(
0,
1,
m_vsConstantBuffer.GetAddressOf()
);
auto a = m_rectangles[j].textureId;
auto b = m_rectangles[j].targetTextureId;
auto srv1 = m_textures[m_rectangles[j].textureId].textureSRV.GetAddressOf();
auto srv2 = m_textures[m_rectangles[j].targetTextureId].textureSRV.GetAddressOf();
ID3D11ShaderResourceView* srvs[2];
srvs[0] = *srv1;
srvs[1] = *srv2;
m_d3dContext->PSSetShaderResources(
0, // starting at the first shader resource slot
2, // set one shader resource binding
srvs
);
m_d3dContext->DrawIndexed(
m_indexCount,
0,
0
);
}
}
Pixel Shader
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix model;
matrix view;
matrix projection;
float opacity;
float3 highlight;
float3 shadow;
float textureTransitionAmount;
};
Texture2D baseTexture : register(t0);
Texture2D targetTexture : register(t1);
SamplerState simpleSampler : register(s0);
struct PixelShaderInput
{
float4 pos : SV_POSITION;
float3 norm : NORMAL;
float2 tex : TEXCOORD0;
};
float4 main(PixelShaderInput input) : SV_TARGET
{
float3 lightDirection = normalize(float3(0, 0, -1));
float4 baseTexelColor = baseTexture.Sample(simpleSampler, input.tex);
float4 targetTexelColor = targetTexture.Sample(simpleSampler, input.tex);
float4 texelColor = lerp(baseTexelColor, targetTexelColor, textureTransitionAmount);
float4 shadedColor;
shadedColor.rgb = lerp(shadow.rgb, highlight.rgb, texelColor.r);
shadedColor.a = texelColor.a * opacity;
return shadedColor;
}
As Jeremiah has suggested, you are not probably moving texture from CPU to GPU for each frame as you would have to create new texture for each frame or using "UpdateSubresource" or "Map/UnMap" methods.
I don't think that instancing is going to help for this specific case, as the number of polygons is extremely low (I would start to worry with several millions of polygons). It is more likely that your application is going to be bandwidth/fillrate limited, as your are performing lots of texture sampling/blending (It depends on tecture fillrate, pixel fillrate and the nunber of ROP on your GPU).
In order to achieve better performance, It is highly recommended to:
Make sure that all your textures have all mipmaps generated. If they
don't have any mipmaps, It will hurt a lot the cache of the GPU. (I also assume that you are using texture.Sample method in HLSL, and not texture.SampleLevel or variants)
Use Direct3D11 Block Compressed texture on the GPU, by using a tool
like texconv.exe or preferably the sample from "Windows DirectX 11
Texture Converter".
On a side note, you will probably get more attention for this kind of question on https://gamedev.stackexchange.com/.
I don't think you are doing any copying back and forth from GPU to system memory. You usually have to explicitly do that a call to Map(...), or by blitting to a texture you created in system memory.
One issue, is you are making a DrawIndexed(...) call for each texture. GPUs work most efficiently if you send it a bunch of work to do by batching. One way to accomplish this is to set n-amount of textures to PSSetShaderResources(i, ...), and do a DrawIndexedInstanced(...). Your shader code would then read each of the shader resources and draw them. I do this in my C++ DirectCanvas code here (SpriteInstanced.cpp). This can make for a lot of code, but the result is very efficient (I even do the matrix ops in the shader for more speed).
One other, maybe a lot easier way, is to give the DirectXTK spritebatch a shot.
I used it here in this project...only for a simple blit but it may be a good start to see the minor amount of setup needed to use the spritebatch.
Also, if possible, try to "atlas" your texture. For instance, try to fit as many "images" in a texture as possible and blit from them vs having a single texture for each.

Resources