Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Need a function to decode a single pixel #562

Open
Atrix256 opened this issue Dec 5, 2024 · 4 comments
Open

Need a function to decode a single pixel #562

Atrix256 opened this issue Dec 5, 2024 · 4 comments
Labels

Comments

@Atrix256
Copy link

Atrix256 commented Dec 5, 2024

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!

@walbourn
Copy link
Member

walbourn commented Dec 6, 2024

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.

@Atrix256
Copy link
Author

Atrix256 commented Dec 6, 2024

Good point. I have something working that I'll share or maybe make a pull request for, once I finish testing.

@Atrix256
Copy link
Author

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;
    }

@Atrix256
Copy link
Author

Atrix256 commented Dec 11, 2024

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;
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants