1

I'm trying convert DDS texture (DXT1 and DXT3 mainly) to ImageData using WebGL. Here is my attemp...

let ext = <WEBGL_compressed_texture_s3tc>gl.getExtension('WEBGL_compressed_texture_s3tc');

let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);

let fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);

gl.compressedTexImage2D(gl.TEXTURE_2D, 0, ext.COMPRESSED_RGBA_S3TC_DXT3_EXT, width, height, 0, sourceImage);

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

let data = new Uint8Array(width * height * 4);

gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);
gl.deleteFramebuffer(fb);

let image = new ImageData(new Uint8ClampedArray(data), width, height);

where gl is WebGLRenderingContext and sourceImage (Uint8Array) is texture in DXT3 format. Without any mipmaps or something. I'm sure, because I tried render this texture using this snippet and it was working.

Code fails at readPixels function with following error (Google Chrome):

[.Offscreen-For-WebGL-000001F2F3C04690]GL ERROR :GL_INVALID_FRAMEBUFFER_OPERATION : glReadPixels: framebuffer incomplete

I'm looking for answer, of course, but without any success. Maybe this may help. I can provide some example textures, if will be needed.

Matěj Pokorný
  • 16,977
  • 5
  • 39
  • 48

1 Answers1

3

You can not render to compressed texture formats, hence attaching them to a framebuffer raises an invalid operation error. You need to attach an uncompressed texture to your framebuffer and then draw a screen-space quad sampling from the DXT texture. Like so:

let ext = <WEBGL_compressed_texture_s3tc>gl.getExtension('WEBGL_compressed_texture_s3tc');

// create framebuffer attachment texture
let colorTarget = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, colorTarget);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
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.texImage2D(
    gl.TEXTURE_2D,
    0,
    gl.RGBA,
    width,
    height,
    0,
    gl.RGBA,
    gl.UNSIGNED_BYTE,
    null
);
// setup framebuffer
let fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTarget, 0);

// create and upload compressed texture
let compressedTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, compressedTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.compressedTexImage2D(gl.TEXTURE_2D, 0, ext.COMPRESSED_RGBA_S3TC_DXT3_EXT, width, height, 0, sourceImage);

gl.viewport(0,0,width,height);
//draw screenspace quad here

// read back uncompressed color data
let data = new Uint8Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);

gl.deleteFramebuffer(fb);

let imageData = new ImageData(new Uint8ClampedArray(data), width, height);
LJᛃ
  • 7,655
  • 2
  • 24
  • 35
  • So... If I understand correctly, I should somehow convert DXT WebGL texture to uncompressed RGBA WebGL texture and then do the things with framebuffer and readPixels? I'm newbie in WebGL, so maybe I understand it wrong...? – Matěj Pokorný Sep 16 '17 at 22:49
  • I've added a modified version of your code snippet to my answer to exemplify the flow – LJᛃ Sep 16 '17 at 23:20
  • It doesn't work :/. I made small demo, when you can try it. Just drag & drop [this texture](https://drive.google.com/uc?id=0B4zf5CmYE7lgRFExZmk1Z3BQTEU&export=download) to resulting window of [this](https://jsfiddle.net/jb8d1pw1/2/) jsfiddle snippet. It will show you texture. But [this](https://jsfiddle.net/dk2coq2g/) snippet with converting texture to imageData is somewhere broken. All bytes in imageData are zeros... – Matěj Pokorný Sep 17 '17 at 00:23
  • yeah because you ought to replace `//draw screenspace quad here` with actual code drawing the screen space quad... – LJᛃ Sep 17 '17 at 01:47
  • Yes! It's working! Thanks! ;-) So, it isn't possible to convert DXT texture to ImageData without rendering everything to Canvas? – Matěj Pokorný Sep 17 '17 at 02:03
  • Well you're rendering to a framebuffer object now, so its not visible on the canvas, on the other hand DXT formats are well documented, you could decode them in plain javascript. – LJᛃ Sep 17 '17 at 02:08