使用 Directx11 的 DXT 压缩纹理的错误运行时更新

Error runtime update of DXT compressed textures with Directx11

上下文: 我正在开发一个本地 C++ Unity 5 插件,它读取 DXT 压缩纹理数据并将其上传到 GPU 以便在 Unity 中进一步使用。目的是创建一个快速的图像序列播放器,即时更新图像数据。使用离线控制台应用程序压缩纹理。 Unity 可以使用不同的图形引擎,我的目标是 DirectX11 和 OpenGL 3.3+。

问题: DirectX 运行时纹理更新代码,通过映射的子资源,在不同的图形驱动程序上提供不同的输出。通过这样的映射资源更新纹理意味着将指针映射到纹理数据并将数据从 RAM 缓冲区 memcpy 到映射的 GPU 缓冲区。这样做,不同的驱动程序似乎在复制字节时期望 行间距 值的不同参数。我在测试过的几个 Nvidia GPU 上从未遇到过问题,但 AMD 和 Intel GPU 似乎表现不同,我得到的输出失真如下图所示。此外,我正在处理 DXT1 像素数据 (0.5bpp) 和 DXT5 数据 (1bpp)。 我似乎无法获得这些 DXT 纹理的正确音高参数。

代码: 以下初始化代码用于生成 d3d11 纹理并用初始纹理数据填充它 - 例如图像序列的第一帧 - 适用于所有驱动程序。 播放器指针 指向一个自定义 class,它处理所有文件读取并包含当前加载的 DXT 压缩帧的 getter,它的尺寸等...

if (s_DeviceType == kUnityGfxRendererD3D11)
    {
        HRESULT hr;
        DXGI_FORMAT format = (compression_type == DxtCompressionType::DXT_TYPE_DXT1_NO_ALPHA) ? DXGI_FORMAT_BC1_UNORM : DXGI_FORMAT_BC3_UNORM;

        // Create texture
        D3D11_TEXTURE2D_DESC desc;
        desc.Width = w;
        desc.Height = h;
        desc.MipLevels = 1;
        desc.ArraySize = 1;
        desc.Format = format;
        // no anti-aliasing
        desc.SampleDesc.Count = 1;
        desc.SampleDesc.Quality = 0;
        desc.Usage = D3D11_USAGE_DYNAMIC;
        desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
        desc.MiscFlags = 0;

        // Initial data: first frame
        D3D11_SUBRESOURCE_DATA data;
        data.pSysMem = player->getBufferPtr();
        data.SysMemPitch = 16 * (player->getWidth() / 4);
        data.SysMemSlicePitch = 0; // just a 2d texture, no depth

        // Init with initial data
        hr = g_D3D11Device->CreateTexture2D(&desc, &data, &dxt_d3d_tex);

        if (SUCCEEDED(hr) && dxt_d3d_tex != 0)
        {
            DXT_VERBOSE("Succesfully created D3D Texture.");

            DXT_VERBOSE("Creating D3D SRV.");
            D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc;
            memset(&SRVDesc, 0, sizeof(SRVDesc));
            SRVDesc.Format = format;
            SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
            SRVDesc.Texture2D.MipLevels = 1;

            hr = g_D3D11Device->CreateShaderResourceView(dxt_d3d_tex, &SRVDesc, &textureView);
            if (FAILED(hr))
            {
                dxt_d3d_tex->Release();
                return hr;
            }
            DXT_VERBOSE("Succesfully created D3D SRV.");
        }
        else
        {
            DXT_ERROR("Error creating D3D texture.")
        }
    }

为每个新帧运行的以下更新代码在某处有错误。请注意包含方法 1 的注释行使用了一个简单的 memcpy,没有指定任何 rowpitch,它在 NVIDIA 驱动程序上运行良好。
您可以在方法 2 中进一步看到我记录了不同的行距值。例如,对于 1920x960 帧,我得到 1920 的缓冲区步幅和 2048 的运行时步幅。这 128 像素的差异可能需要填充(如下面的示例图片所示),但我不知道如何填充。当我只使用 mappedResource.RowPitch 而不将其除以 4(通过移位完成)时,Unity 崩溃。

    ID3D11DeviceContext* ctx = NULL;
    g_D3D11Device->GetImmediateContext(&ctx);

    if (dxt_d3d_tex && bShouldUpload)
    {
        if (player->gather_stats) before_upload = ns();

        D3D11_MAPPED_SUBRESOURCE mappedResource;
        ctx->Map(dxt_d3d_tex, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);

        /* 1: THIS CODE WORKS ON ALL NVIDIA DRIVERS BUT GENERATES DISTORTED OR NO OUTPUT ON AMD/INTEL: */
        //memcpy(mappedResource.pData, player->getBufferPtr(), player->getBytesPerFrame());

        /* 2: THIS CODE GENERATES OUTPUT BUT SEEMS TO NEED PADDING? */
        BYTE* mappedData = reinterpret_cast<BYTE*>(mappedResource.pData);
        BYTE* buffer = player->getBufferPtr();
        UINT height = player->getHeight();
        UINT buffer_stride = player->getBytesPerFrame() / player->getHeight();
        UINT runtime_stride = mappedResource.RowPitch >> 2;

        DXT_VERBOSE("Buffer stride: %d", buffer_stride);
        DXT_VERBOSE("Runtime stride: %d", runtime_stride);

        for (UINT i = 0; i < height; ++i)
        {
            memcpy(mappedData, buffer, buffer_stride);
            mappedData += runtime_stride;
            buffer += buffer_stride;
        }

        ctx->Unmap(dxt_d3d_tex, 0);
    }

示例图片 1 - 在 AMD/INTEL(方法 1)

上使用 memcpy 复制整个缓冲区而不使用单独的行距时输出失真

示例图片 2 - 将上述代码与 AMD/INTEL 上的 mappedResource.RowPitch 一起使用时输出更好但仍然错误(方法 2)。蓝色条表示错误区域,需要消失才能使所有像素对齐并形成一幅图像。

感谢指点! 最好, 文森特

映射的数据行间距是byte,除以4肯定有问题

UINT runtime_stride = mappedResource.RowPitch >> 2;
...
mappedData += runtime_stride; // here you are only jumping one quarter of a row

BC格式的身高数除以4

此外,BC1 格式是每个 4x4 块 8 个字节,因此下面的行应该是 8 * 而不是 16 *,但是只要您正确处理行跨度,d3d 就会明白了,你只是在这里浪费了一半的内存。

data.SysMemPitch = 16 * (player->getWidth() / 4);