DirectXTex
DirectXTex copied to clipboard
Need a function to decode a single pixel
Thanks for this excellent library!
In a graphics development / debugging application (https://github.com/electronicarts/gigi/) we have a feature where a user can click on a pixel of an image being shown in a debug context, and the UI will show the value of the pixel.
This is done by reading back the resource and displaying the values under the pixel.
This works fine for uncompressed formats, but for compressed formats, it's decompressing the entire texture.
It would be really nice if there was an interface where you could ask for a single pixel, and it could internally do the minimal amount of work needed to get the value - like maybe decoding the single block containing that pixel.
I found this library which can help, but it doesn't support BC6, and it would be nice to use your library, as I'm using it to load and save dds images. https://github.com/richgel999/bc7enc
Thank you!
You could pretty easily do the decompression of a single block with the existing library. You just need to fix up the pixels pointer and tell the library to decompress a 4x4 image.
Good point. I have something working that I'll share or maybe make a pull request for, once I finish testing.
This needs to support mips and isn't pull request ready as is, but might be useful for other people to follow along with. I'll update this (and again, maybe a pull request), when it has mip support.
static bool DecodeBCnPixel(ID3D12Resource* readbackResource, D3D12_RESOURCE_DESC resourceOriginalDesc, int width, int height, int depth, int x, int y, int z, std::vector<unsigned char>& pixel, DXGI_FORMAT& pixelFormat)
{
// Calculate where the block is that we care about, so we can decompress just a single block
size_t blockOffset = 0;
size_t blockSize = 0;
{
size_t rowPitch = 0;
size_t slicePitch = 0;
HRESULT hr = DirectX::ComputePitch(resourceOriginalDesc.Format, width, height, rowPitch, slicePitch);
if (FAILED(hr))
return false;
blockSize = DirectX::BytesPerBlock(resourceOriginalDesc.Format);
size_t blockRowSize = rowPitch;
size_t blockOffsetX = x / 4;
size_t blockOffsetY = y / 4;
blockOffset = z * slicePitch + blockOffsetY * blockRowSize + blockOffsetX * blockSize;
}
// set up compressed image data
DirectX::Image compressedImages;
compressedImages.width = 4;
compressedImages.height = 4;
compressedImages.format = resourceOriginalDesc.Format;
HRESULT hr = DirectX::ComputePitch(compressedImages.format, compressedImages.width, compressedImages.height, compressedImages.rowPitch, compressedImages.slicePitch);
if (FAILED(hr))
return false;
// Map the block we care about
D3D12_RANGE readRange;
readRange.Begin = blockOffset;
readRange.End = blockOffset + blockSize;
uint8_t* readbackData = nullptr;
hr = readbackResource->Map(0, &readRange, (void**)&readbackData);
if (FAILED(hr))
return false;
// Decompress the block
compressedImages.pixels = &readbackData[blockOffset];
DirectX::ScratchImage decompressedImages;
hr = DirectX::Decompress(compressedImages, DXGI_FORMAT_UNKNOWN, decompressedImages);
// Unmap
D3D12_RANGE writeRange;
writeRange.Begin = 1;
writeRange.End = 0;
readbackResource->Unmap(0, &writeRange);
if (FAILED(hr))
return false;
// Copy the specific pixel we care about and return success
const DirectX::Image* decompressedImage = decompressedImages.GetImage(0, 0, 0);
pixelFormat = decompressedImages.GetMetadata().format;
size_t pixelBytes = DirectX::BitsPerPixel(pixelFormat) / 8;
pixel.resize(pixelBytes);
memcpy(pixel.data(), &decompressedImage->pixels[(y % 4) * decompressedImage->rowPitch + (x % 4) * pixelBytes], pixelBytes);
return true;
}
Here's a version that takes mips into account and seems to work for me.
It would be nice if there were a function like this in the API, that would give you a specific pixel value, from whatever type of texture / format you had, or perhaps a rectangle of pixel values if people would get use out of that.
static bool DecodeBCnPixel(ID3D12Device2* device, ID3D12Resource* readbackResource, D3D12_RESOURCE_DESC resourceOriginalDesc, int width, int height, int depth, int x, int y, int z, int mipIndex, std::vector<unsigned char>& pixel, DXGI_FORMAT& pixelFormat)
{
// Calculate the subresource that we care about
z = std::max(0, std::min(depth - 1, z));
bool is3D = (resourceOriginalDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D);
unsigned int subresourceIndex = D3D12CalcSubresource(mipIndex, is3D ? 0 : z, 0, resourceOriginalDesc.MipLevels, is3D ? 1 : depth);
// Get information about the subresources
unsigned int numSubResources = is3D
? resourceOriginalDesc.MipLevels
: resourceOriginalDesc.MipLevels * resourceOriginalDesc.DepthOrArraySize;
std::vector<unsigned char> layoutsMem((sizeof(D3D12_PLACED_SUBRESOURCE_FOOTPRINT) + sizeof(UINT) + sizeof(UINT64)) * numSubResources);
std::vector<unsigned int> numRows(numSubResources);
D3D12_PLACED_SUBRESOURCE_FOOTPRINT* layouts = (D3D12_PLACED_SUBRESOURCE_FOOTPRINT*)layoutsMem.data();
device->GetCopyableFootprints(&resourceOriginalDesc, 0, numSubResources, 0, layouts, numRows.data(), nullptr, nullptr);
// Ensure params are in range
const D3D12_PLACED_SUBRESOURCE_FOOTPRINT& layout = layouts[subresourceIndex];
x = std::max<UINT>(0, std::min<UINT>(layout.Footprint.Width - 1, x));
y = std::max<UINT>(0, std::min<UINT>(layout.Footprint.Height - 1, y));
if (is3D)
z = std::max<UINT>(0, std::min<UINT>(layout.Footprint.Depth - 1, z));
else
z = 0;
// Calculate where the block is that we care about, so we can decompress just a single block
size_t blockOffset = 0;
size_t blockSize = 0;
{
size_t rowPitch = layout.Footprint.RowPitch;
size_t slicePitch = layout.Footprint.RowPitch * numRows[subresourceIndex];
blockSize = DirectX::BytesPerBlock(resourceOriginalDesc.Format);
size_t blockRowSize = rowPitch;
size_t blockOffsetX = x / 4;
size_t blockOffsetY = y / 4;
blockOffset = layout.Offset + z * slicePitch + blockOffsetY * blockRowSize + blockOffsetX * blockSize;
}
// set up compressed image data
DirectX::Image compressedImages;
compressedImages.width = 4;
compressedImages.height = 4;
compressedImages.format = resourceOriginalDesc.Format;
HRESULT hr = DirectX::ComputePitch(compressedImages.format, compressedImages.width, compressedImages.height, compressedImages.rowPitch, compressedImages.slicePitch);
if (FAILED(hr))
return false;
// Map the block we care about
D3D12_RANGE readRange;
readRange.Begin = blockOffset;
readRange.End = blockOffset + blockSize;
uint8_t* readbackData = nullptr;
hr = readbackResource->Map(0, &readRange, (void**)&readbackData);
if (FAILED(hr))
return false;
// Decompress the block
compressedImages.pixels = &readbackData[blockOffset];
DirectX::ScratchImage decompressedImages;
hr = DirectX::Decompress(compressedImages, DXGI_FORMAT_UNKNOWN, decompressedImages);
// Unmap
D3D12_RANGE writeRange;
writeRange.Begin = 1;
writeRange.End = 0;
readbackResource->Unmap(0, &writeRange);
if (FAILED(hr))
return false;
// Copy the specific pixel we care about and return success
const DirectX::Image* decompressedImage = decompressedImages.GetImage(0, 0, 0);
pixelFormat = decompressedImages.GetMetadata().format;
size_t pixelBytes = DirectX::BitsPerPixel(pixelFormat) / 8;
pixel.resize(pixelBytes);
memcpy(pixel.data(), &decompressedImage->pixels[(y % 4) * decompressedImage->rowPitch + (x % 4) * pixelBytes], pixelBytes);
return true;
}