bool IsDepthStencilFormat(GLenum internalFormat)
{
	if(IsCompressedFormat(internalFormat))
		return false;

	GLenum fmt = GetBaseFormat(internalFormat);

	return (fmt == eGL_DEPTH_COMPONENT || fmt == eGL_STENCIL || fmt == eGL_DEPTH_STENCIL);
}
//--------------------------------------------------------------------------------------
// PixelBuffer
//--------------------------------------------------------------------------------------
D3D12_RESOURCE_DESC PixelBuffer::DescribeTex2D( uint32_t Width, uint32_t Height, uint32_t DepthOrArraySize,
	uint32_t NumMips, DXGI_FORMAT Format, UINT Flags )
{
	m_Width = Width;
	m_Height = Height;
	m_ArraySize = DepthOrArraySize;
	m_Format = Format;

	D3D12_RESOURCE_DESC Desc = {};
	Desc.Alignment = 0;
	Desc.DepthOrArraySize = (UINT16)DepthOrArraySize;
	Desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
	Desc.Flags = (D3D12_RESOURCE_FLAGS)Flags;
	Desc.Format = GetBaseFormat( Format );
	Desc.Height = (UINT)Height;
	Desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
	Desc.MipLevels = NumMips;
	Desc.SampleDesc.Count = 1;
	Desc.SampleDesc.Quality = 0;
	Desc.Width = (UINT64)Width;
	return Desc;
}
	void Texture2D::SetPixels(byte* pixels) {
		GL(glBindTexture(GL_TEXTURE_2D, m_Texture));
		GL(glTexImage2D(GL_TEXTURE_2D, 0, static_cast<GLenum>(m_Format), m_Width, m_Height, 0, static_cast<GLenum>(GetBaseFormat(m_Format)), GL_UNSIGNED_BYTE, pixels));
	}
	Texture2D::Texture2D(byte* pixels, u32 width, u32 height, TextureFormat format) : Texture(width, height, TextureType::TEXTURE_2D, format) {
		GL(glGenTextures(1, &m_Texture));
		GL(glBindTexture(GL_TEXTURE_2D, m_Texture));
		GL(glTexImage2D(GL_TEXTURE_2D, 0, static_cast<GLenum>(format), width, height, 0, static_cast<GLenum>(GetBaseFormat(format)), GL_UNSIGNED_BYTE, pixels));
		SetSamplerState();
	}
Exemple #5
0
size_t GetCompressedByteSize(GLsizei w, GLsizei h, GLsizei d, GLenum internalformat, int mip)
{
	if(!IsCompressedFormat(internalformat))
	{
		RDCERR("Not compressed format");
		return GetByteSize(w, h, d, GetBaseFormat(internalformat), GetDataType(internalformat));
	}

	switch(internalformat)
	{
		// BC1
		case eGL_COMPRESSED_RGB_S3TC_DXT1_EXT:
		case eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
		case eGL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
		case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
			return (AlignUp4(w) * AlignUp4(h) * d) / 2;
		// BC2
		case eGL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
		case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
			return (AlignUp4(w) * AlignUp4(h) * d);
		// BC3
		case eGL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
		case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
			return (AlignUp4(w) * AlignUp4(h) * d);
		// BC4
		case eGL_COMPRESSED_RED_RGTC1:
		case eGL_COMPRESSED_SIGNED_RED_RGTC1:
			return (AlignUp4(w) * AlignUp4(h) * d) / 2;
		// BC5
		case eGL_COMPRESSED_RG_RGTC2:
		case eGL_COMPRESSED_SIGNED_RG_RGTC2:
			return (AlignUp4(w) * AlignUp4(h) * d);
		// BC6
		case eGL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB:
		case eGL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB:
			return (AlignUp4(w) * AlignUp4(h) * d);
		// BC7
		case eGL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
		case eGL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
			return (AlignUp4(w) * AlignUp4(h) * d);
		// ETC2
		case eGL_COMPRESSED_RGB8_ETC2:
		case eGL_COMPRESSED_SRGB8_ETC2:
		case eGL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
		case eGL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
			return (AlignUp4(w) * AlignUp4(h) * d) / 2;
		// EAC
		case eGL_COMPRESSED_RGBA8_ETC2_EAC:
		case eGL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
			return (AlignUp4(w) * AlignUp4(h) * d);
		case eGL_COMPRESSED_R11_EAC:
		case eGL_COMPRESSED_SIGNED_R11_EAC:
			return (AlignUp4(w) * AlignUp4(h) * d) / 2;
		case eGL_COMPRESSED_RG11_EAC:
		case eGL_COMPRESSED_SIGNED_RG11_EAC:
			return (AlignUp4(w) * AlignUp4(h) * d);
		default:
			break;
	}

	RDCERR("Unrecognised compressed format");
	return GetByteSize(w, h, d, GetBaseFormat(internalformat), GetDataType(internalformat));
}
Exemple #6
0
size_t GetCompressedByteSize(GLsizei w, GLsizei h, GLsizei d, GLenum internalformat, int mip)
{
  if(!IsCompressedFormat(internalformat))
  {
    RDCERR("Not compressed format %s", ToStr::Get(internalformat).c_str());
    return GetByteSize(w, h, d, GetBaseFormat(internalformat), GetDataType(internalformat));
  }

  uint32_t astc[2] = {0, 0};

  switch(internalformat)
  {
    // BC1
    case eGL_COMPRESSED_RGB_S3TC_DXT1_EXT:
    case eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
    case eGL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
    case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
      return (AlignUp4(w) * AlignUp4(h) * d) / 2;
    // BC2
    case eGL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
    case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
      return (AlignUp4(w) * AlignUp4(h) * d);
    // BC3
    case eGL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
    case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
      return (AlignUp4(w) * AlignUp4(h) * d);
    // BC4
    case eGL_COMPRESSED_RED_RGTC1:
    case eGL_COMPRESSED_SIGNED_RED_RGTC1:
      return (AlignUp4(w) * AlignUp4(h) * d) / 2;
    // BC5
    case eGL_COMPRESSED_RG_RGTC2:
    case eGL_COMPRESSED_SIGNED_RG_RGTC2:
      return (AlignUp4(w) * AlignUp4(h) * d);
    // BC6
    case eGL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB:
    case eGL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB:
      return (AlignUp4(w) * AlignUp4(h) * d);
    // BC7
    case eGL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
    case eGL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
      return (AlignUp4(w) * AlignUp4(h) * d);
    // ETC2
    case eGL_COMPRESSED_RGB8_ETC2:
    case eGL_COMPRESSED_SRGB8_ETC2:
    case eGL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
    case eGL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
      return (AlignUp4(w) * AlignUp4(h) * d) / 2;
    // EAC
    case eGL_COMPRESSED_RGBA8_ETC2_EAC:
    case eGL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: return (AlignUp4(w) * AlignUp4(h) * d);
    case eGL_COMPRESSED_R11_EAC:
    case eGL_COMPRESSED_SIGNED_R11_EAC: return (AlignUp4(w) * AlignUp4(h) * d) / 2;
    case eGL_COMPRESSED_RG11_EAC:
    case eGL_COMPRESSED_SIGNED_RG11_EAC: return (AlignUp4(w) * AlignUp4(h) * d);
    case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
      astc[0] = 4;
      astc[1] = 4;
      break;
    case GL_COMPRESSED_RGBA_ASTC_5x4_KHR:
      astc[0] = 5;
      astc[1] = 4;
      break;
    case GL_COMPRESSED_RGBA_ASTC_5x5_KHR:
      astc[0] = 5;
      astc[1] = 5;
      break;
    case GL_COMPRESSED_RGBA_ASTC_6x5_KHR:
      astc[0] = 6;
      astc[1] = 5;
      break;
    case GL_COMPRESSED_RGBA_ASTC_6x6_KHR:
      astc[0] = 6;
      astc[1] = 6;
      break;
    case GL_COMPRESSED_RGBA_ASTC_8x5_KHR:
      astc[0] = 8;
      astc[1] = 5;
      break;
    case GL_COMPRESSED_RGBA_ASTC_8x6_KHR:
      astc[0] = 8;
      astc[1] = 6;
      break;
    case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:
      astc[0] = 8;
      astc[1] = 8;
      break;
    case GL_COMPRESSED_RGBA_ASTC_10x5_KHR:
      astc[0] = 10;
      astc[1] = 5;
      break;
    case GL_COMPRESSED_RGBA_ASTC_10x6_KHR:
      astc[0] = 10;
      astc[1] = 6;
      break;
    case GL_COMPRESSED_RGBA_ASTC_10x8_KHR:
      astc[0] = 10;
      astc[1] = 8;
      break;
    case GL_COMPRESSED_RGBA_ASTC_10x10_KHR:
      astc[0] = 10;
      astc[1] = 10;
      break;
    case GL_COMPRESSED_RGBA_ASTC_12x10_KHR:
      astc[0] = 12;
      astc[1] = 10;
      break;
    case GL_COMPRESSED_RGBA_ASTC_12x12_KHR:
      astc[0] = 12;
      astc[1] = 12;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
      astc[0] = 4;
      astc[1] = 4;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:
      astc[0] = 5;
      astc[1] = 4;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:
      astc[0] = 5;
      astc[1] = 5;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:
      astc[0] = 6;
      astc[1] = 5;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:
      astc[0] = 6;
      astc[1] = 6;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:
      astc[0] = 8;
      astc[1] = 5;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:
      astc[0] = 8;
      astc[1] = 6;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
      astc[0] = 8;
      astc[1] = 8;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:
      astc[0] = 10;
      astc[1] = 5;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:
      astc[0] = 10;
      astc[1] = 6;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:
      astc[0] = 10;
      astc[1] = 8;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:
      astc[0] = 10;
      astc[1] = 10;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:
      astc[0] = 12;
      astc[1] = 10;
      break;
    case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:
      astc[0] = 12;
      astc[1] = 12;
      break;
    default: break;
  }

  if(astc[0] > 0 && astc[1] > 0)
  {
    uint32_t blocks[2] = {(w / astc[0]), (h / astc[1])};

    // how many blocks are needed - including any extra partial blocks
    blocks[0] += (w % astc[0]) ? 1 : 0;
    blocks[1] += (h % astc[1]) ? 1 : 0;

    // ASTC blocks are all 128 bits each
    return blocks[0] * blocks[1] * 16 * d;
  }

  RDCERR("Unrecognised compressed format %s", ToStr::Get(internalformat).c_str());
  return GetByteSize(w, h, d, GetBaseFormat(internalformat), GetDataType(internalformat));
}
Exemple #7
0
void csGLRender2TextureFramebuf::GrabFramebuffer (const RTAttachment<>& target,
						  InternalFormatClass fmtClass)
{
  csGLBasicTextureHandle* tex_mm = 
    static_cast<csGLBasicTextureHandle*> ((iTextureHandle*)target.texture);
  tex_mm->Precache ();
  // Texture is in tha cache, update texture directly.
  G3D->ActivateTexture (tex_mm);

  GLenum internalFormat = 0;

  /* Texture has a keycolor - so we need to deal specially with it
    * to make sure the keycolor gets transparent. */
  if (tex_mm->GetKeyColor ())
  {
    // @@@ Processing sucks, but how else to handle keycolor?
    const size_t numPix = txt_w * txt_h;
    pixelScratch.SetSize (numPix * 4);
    glReadPixels (0, 0, txt_w, txt_h, GL_RGBA, 
      GL_UNSIGNED_BYTE, pixelScratch.GetArray());
    csRGBpixel key;
    tex_mm->GetKeyColor (key.red, key.green, key.blue);
    csBakeKeyColor::RGBA2D (pixelScratch.GetArray(), 
      pixelScratch.GetArray(), txt_w, txt_h, key);
    tex_mm->Blit (0, 0, txt_w, txt_h, pixelScratch.GetArray());
  }
  else
  {
    GLenum textarget = tex_mm->GetGLTextureTarget();
    if ((textarget != GL_TEXTURE_2D)
        && (textarget != GL_TEXTURE_3D)  
        && (textarget != GL_TEXTURE_RECTANGLE_ARB) 
	&& (textarget != GL_TEXTURE_CUBE_MAP))
      return;

    bool handle_subtexture = (textarget == GL_TEXTURE_CUBE_MAP);
    bool handle_3d = (textarget == GL_TEXTURE_3D);
    /* Reportedly, some drivers crash if using CopyTexImage on a texture
      * size larger than the framebuffer. Use CopyTexSubImage then. */
    bool needSubImage = (txt_w > viewportHelper.GetVPWidth()) 
      || (txt_h > viewportHelper.GetVPHeight());
    // Texture was not used as a render target before.
    // Make some necessary adjustments.
    if (needSubImage)
    {
      if (!tex_mm->IsWasRenderTarget())
      {
	internalFormat = GetInternalFormat (fmtClass, tex_mm);
	GLenum baseFormat = GetBaseFormat (fmtClass, tex_mm);
  
	tex_mm->SetupAutoMipping();
	tex_mm->SetWasRenderTarget (true);
	tex_mm->texFormat = iTextureHandle::RGBA8888;
	// Gah. Turn target texture to required storage.
	if (handle_subtexture)
	{
	  for (int i = 0; i < 6; i++)
	    glTexImage2D (
	      GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + i, 0, internalFormat, 
	      txt_w, txt_h,0, baseFormat, GL_UNSIGNED_BYTE, 0);
	}
	else if (handle_3d)
	{
	  G3D->ext->glTexImage3D (textarget, 0, internalFormat, txt_w, txt_h, 
	    txt_d, 0, baseFormat, GL_UNSIGNED_BYTE, 0);
	}
	else
	  glTexImage2D (textarget, 0, internalFormat, txt_w, txt_h, 
	    0, baseFormat, GL_UNSIGNED_BYTE, 0);
      }
      int orgX = viewportHelper.GetVPOfsX();
      int orgY = viewportHelper.GetOriginalFramebufferHeight()
	- (viewportHelper.GetVPOfsY() 
	  + csMin (txt_h, viewportHelper.GetVPHeight()));
    
      if (handle_subtexture)
	glCopyTexSubImage2D (
	  GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + target.subtexture,
	  0, 0, 0, orgX, orgY, 
	  csMin (txt_w, viewportHelper.GetVPWidth()), 
	  csMin (txt_h, viewportHelper.GetVPHeight()));
      else if (handle_3d)
	G3D->ext->glCopyTexSubImage3D (textarget, 0, 0, 0, orgX, orgY,
	  target.subtexture,
	  csMin (txt_w, viewportHelper.GetVPWidth()),
	  csMin (txt_h, viewportHelper.GetVPHeight()));
      else
	glCopyTexSubImage2D (textarget, 0, 0, 0, orgX, orgY, 
	  csMin (txt_w, viewportHelper.GetVPWidth()),
	  csMin (txt_h, viewportHelper.GetVPHeight()));
    }
    else
    {
      int orgX = viewportHelper.GetVPOfsX();
      int orgY = viewportHelper.GetOriginalFramebufferHeight()
	- (viewportHelper.GetVPOfsY() + txt_h);
    
      if (!tex_mm->IsWasRenderTarget())
      {
	internalFormat = GetInternalFormat (fmtClass, tex_mm);
  
	tex_mm->SetupAutoMipping();
	tex_mm->SetWasRenderTarget (true);
	tex_mm->texFormat = iTextureHandle::RGBA8888;
	if (handle_subtexture)
	  glCopyTexImage2D (GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + target.subtexture, 
	    0, internalFormat, orgX, orgY, txt_w, txt_h, 0);
	else if (handle_3d)
	{
	  // Gah. Turn target texture to required storage.
	  GLenum baseFormat = GetBaseFormat (fmtClass, tex_mm);
	  G3D->ext->glTexImage3D (textarget, 0, internalFormat, txt_w, txt_h, 
	    txt_d, 0, baseFormat, GL_UNSIGNED_BYTE, 0);
	  G3D->ext->glCopyTexSubImage3D (textarget, 0, 0, 0, orgX, orgY,
	    target.subtexture, txt_w, txt_h);
	}
	else
	  glCopyTexImage2D (textarget, 0, internalFormat,
	    orgX, orgY, txt_w, txt_h, 0);
      }
      else
      {
	glGetTexLevelParameteriv ((textarget == GL_TEXTURE_CUBE_MAP) 
	    ? GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB : textarget, 
	  0, GL_TEXTURE_INTERNAL_FORMAT, (GLint*)&internalFormat);
	
	if (handle_subtexture)
	  glCopyTexSubImage2D (GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + target.subtexture, 
	    0, 0, 0, orgX, orgY, txt_w, txt_h);
	else if (handle_3d)
	  G3D->ext->glCopyTexSubImage3D (textarget, 0, 0, 0, orgX, orgY,
	    target.subtexture, txt_w, txt_h);
	else
	  glCopyTexSubImage2D (textarget, 0,
	    0, 0, orgX, orgY, txt_w, txt_h);
      }
    }
    tex_mm->RegenerateMipmaps();
  }
}
Exemple #8
0
bool WrappedOpenGL::Serialise_wglDXLockObjectsNV(SerialiserType &ser, GLResource Resource)
{
  SERIALISE_ELEMENT(Resource);
  SERIALISE_ELEMENT_LOCAL(textype, Resource.Namespace == eResBuffer
                                       ? eGL_NONE
                                       : m_Textures[GetResourceManager()->GetID(Resource)].curType)
      .Hidden();

  const GLHookSet &gl = m_Real;

  // buffer contents are easier to save
  if(textype == eGL_NONE)
  {
    byte *Contents = NULL;
    uint32_t length = 1;

    // while writing, fetch the buffer's size and contents
    if(ser.IsWriting())
    {
      gl.glGetNamedBufferParameterivEXT(Resource.name, eGL_BUFFER_SIZE, (GLint *)&length);

      Contents = new byte[length];

      GLuint oldbuf = 0;
      gl.glGetIntegerv(eGL_COPY_READ_BUFFER_BINDING, (GLint *)&oldbuf);
      gl.glBindBuffer(eGL_COPY_READ_BUFFER, Resource.name);

      gl.glGetBufferSubData(eGL_COPY_READ_BUFFER, 0, (GLsizeiptr)length, Contents);

      gl.glBindBuffer(eGL_COPY_READ_BUFFER, oldbuf);
    }

    SERIALISE_ELEMENT_ARRAY(Contents, length);

    SERIALISE_CHECK_READ_ERRORS();

    // restore on replay
    if(IsReplayingAndReading())
    {
      uint32_t liveLength = 1;
      gl.glGetNamedBufferParameterivEXT(Resource.name, eGL_BUFFER_SIZE, (GLint *)&liveLength);

      gl.glNamedBufferSubData(Resource.name, 0, (GLsizeiptr)RDCMIN(length, liveLength), Contents);
    }
  }
  else
  {
    GLuint ppb = 0, pub = 0;
    PixelPackState pack;
    PixelUnpackState unpack;

    // save and restore pixel pack/unpack state. We only need one or the other but for clarity we
    // push and pop both always.
    if(ser.IsWriting() || !IsStructuredExporting(m_State))
    {
      gl.glGetIntegerv(eGL_PIXEL_PACK_BUFFER_BINDING, (GLint *)&ppb);
      gl.glGetIntegerv(eGL_PIXEL_UNPACK_BUFFER_BINDING, (GLint *)&pub);
      gl.glBindBuffer(eGL_PIXEL_PACK_BUFFER, 0);
      gl.glBindBuffer(eGL_PIXEL_UNPACK_BUFFER, 0);

      pack.Fetch(&gl, false);
      unpack.Fetch(&gl, false);

      ResetPixelPackState(gl, false, 1);
      ResetPixelUnpackState(gl, false, 1);
    }

    TextureData &details = m_Textures[GetResourceManager()->GetID(Resource)];
    GLuint tex = Resource.name;

    // serialise the metadata for convenience
    SERIALISE_ELEMENT_LOCAL(internalFormat, details.internalFormat).Hidden();
    SERIALISE_ELEMENT_LOCAL(width, details.width).Hidden();
    SERIALISE_ELEMENT_LOCAL(height, details.height).Hidden();
    SERIALISE_ELEMENT_LOCAL(depth, details.depth).Hidden();

    RDCASSERT(internalFormat == details.internalFormat, internalFormat, details.internalFormat);
    RDCASSERT(width == details.width, width, details.width);
    RDCASSERT(height == details.height, height, details.height);
    RDCASSERT(depth == details.depth, depth, details.depth);

    GLenum fmt = GetBaseFormat(internalFormat);
    GLenum type = GetDataType(internalFormat);

    GLint dim = details.dimension;

    uint32_t size = (uint32_t)GetByteSize(width, height, depth, fmt, type);

    int mips = 0;
    if(IsReplayingAndReading())
      mips = GetNumMips(gl, textype, tex, width, height, depth);

    byte *scratchBuf = NULL;

    // on read and write, we allocate a single buffer big enough for all mips and re-use it
    // to avoid repeated new/free.
    scratchBuf = AllocAlignedBuffer(size);

    GLuint prevtex = 0;
    if(!IsStructuredExporting(m_State))
    {
      gl.glGetIntegerv(TextureBinding(details.curType), (GLint *)&prevtex);
      gl.glBindTexture(textype, tex);
    }

    for(int i = 0; i < mips; i++)
    {
      int w = RDCMAX(details.width >> i, 1);
      int h = RDCMAX(details.height >> i, 1);
      int d = RDCMAX(details.depth >> i, 1);

      if(textype == eGL_TEXTURE_CUBE_MAP_ARRAY || textype == eGL_TEXTURE_1D_ARRAY ||
         textype == eGL_TEXTURE_2D_ARRAY)
        d = details.depth;

      size = (uint32_t)GetByteSize(w, h, d, fmt, type);

      GLenum targets[] = {
          eGL_TEXTURE_CUBE_MAP_POSITIVE_X, eGL_TEXTURE_CUBE_MAP_NEGATIVE_X,
          eGL_TEXTURE_CUBE_MAP_POSITIVE_Y, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
          eGL_TEXTURE_CUBE_MAP_POSITIVE_Z, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
      };

      int count = ARRAY_COUNT(targets);

      if(textype != eGL_TEXTURE_CUBE_MAP)
      {
        targets[0] = textype;
        count = 1;
      }

      for(int trg = 0; trg < count; trg++)
      {
        if(ser.IsWriting())
        {
          // we avoid glGetTextureImageEXT as it seems buggy for cubemap faces
          gl.glGetTexImage(targets[trg], i, fmt, type, scratchBuf);
        }

        // serialise without allocating memory as we already have our scratch buf sized.
        ser.Serialise("SubresourceContents", scratchBuf, size, SerialiserFlags::NoFlags);

        if(IsReplayingAndReading() && !ser.IsErrored())
        {
          if(dim == 1)
            gl.glTextureSubImage1DEXT(tex, targets[trg], i, 0, w, fmt, type, scratchBuf);
          else if(dim == 2)
            gl.glTextureSubImage2DEXT(tex, targets[trg], i, 0, 0, w, h, fmt, type, scratchBuf);
          else if(dim == 3)
            gl.glTextureSubImage3DEXT(tex, targets[trg], i, 0, 0, 0, w, h, d, fmt, type, scratchBuf);
        }
      }
    }

    FreeAlignedBuffer(scratchBuf);

    // restore pixel (un)packing state
    if(ser.IsWriting() || !IsStructuredExporting(m_State))
    {
      gl.glBindBuffer(eGL_PIXEL_PACK_BUFFER, ppb);
      gl.glBindBuffer(eGL_PIXEL_UNPACK_BUFFER, pub);
      pack.Apply(&gl, false);
      unpack.Apply(&gl, false);
    }

    if(!IsStructuredExporting(m_State))
      gl.glBindTexture(textype, prevtex);

    SERIALISE_CHECK_READ_ERRORS();
  }

  return true;
}