Display Duplication API帧缓冲区对齐异常求助:1366分辨率每行少2字节
解决Display Duplication API捕获非4倍数分辨率时的BGRA转RGB对齐问题及向量化优化
问题核心分析
当捕获1366x768这类水平分辨率非4倍数的桌面时,BGRA帧缓冲区出现每行少2字节的错位问题,本质是Display Duplication API返回的纹理存在硬件层面的隐式对齐——即使RowPitch等于output.width * 4,实际纹理的行数据边界未必与逻辑分辨率完全匹配。你的转换代码依赖RowPitch计算行偏移,但忽略了硬件对非对齐分辨率做的字节级调整,导致后续行的像素读取错位。
修复对齐问题的步骤
1. 确认纹理的实际物理宽度
不要直接使用桌面分辨率作为output.width,从捕获到的ID3D11Texture2D中获取真实的纹理参数:
D3D11_TEXTURE2D_DESC texDesc; capturedTexture->GetDesc(&texDesc); UINT actualWidth = texDesc.Width; UINT actualRowPitch = texDesc.Width * 4; // BGRA格式下的行字节数
这里的actualWidth是硬件对齐后的纹理宽度,需以此为基准计算行偏移。
2. 修正行偏移计算
如果actualWidth大于逻辑桌面宽度,每行处理完后,源指针需要跳过(actualWidth - output.width) * 4字节;若RowPitch等于output.width * 4但仍有错位(如你的1366像素场景),说明硬件在每行末尾填充了2字节,需手动调整:
unsigned char* dstPtr = output.RGBOut; const unsigned char* srcPtr = static_cast<unsigned char*>(mappedResource.pData); UINT skipBytes = mappedResource.RowPitch - output.width * 4; // 针对宽度模4余2的场景,手动补充偏移 if (skipBytes == 0 && (output.width % 4) == 2) { skipBytes = 2; } for(int y = 0; y < output.height; ++y){ for(int x = 0; x < output.width; ++x){ dstPtr[0] = srcPtr[0]; dstPtr[1] = srcPtr[1]; dstPtr[2] = srcPtr[2]; srcPtr += 4; dstPtr += 3; } srcPtr += skipBytes; }
3. 更可靠的方案:用D3D11视图裁剪逻辑分辨率
创建着色器资源视图(SRV),仅裁剪到逻辑桌面分辨率,确保读取时只获取有效区域的数据:
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; ZeroMemory(&srvDesc, sizeof(srvDesc)); srvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = 1; srvDesc.Texture2D.MostDetailedMip = 0; ID3D11ShaderResourceView* srv = nullptr; device->CreateShaderResourceView(capturedTexture, &srvDesc, &srv);
后续通过该SRV读取数据,可彻底规避对齐带来的额外字节干扰。
向量化优化方案
手动循环难以向量化,推荐两种高效方式:
1. 使用SSE/AVX SIMD指令集
利用CPU的SIMD单元批量处理像素,提升转换速度:
#include <smmintrin.h> // SSE4.1 for(int y = 0; y < output.height; ++y){ __m128i* srcSIMD = reinterpret_cast<__m128i*>(const_cast<unsigned char*>(srcPtr)); __m128i* dstSIMD = reinterpret_cast<__m128i*>(dstPtr); int x = 0; // 批量处理4个BGRA像素转RGB for(; x < output.width - 3; x +=4){ __m128i bgra = _mm_loadu_si128(srcSIMD); // 提取B、G、R通道,丢弃Alpha __m128i bg = _mm_shuffle_epi8(bgra, _mm_set_epi8(-1,-1,-1,-1, 2,1,0,-1, 6,5,4,-1, 10,9,8,-1)); __m128i r = _mm_shuffle_epi8(bgra, _mm_set_epi8(-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 14,13,12,-1)); __m128i rgb0 = _mm_or_si128(bg, r); // 写入12字节RGB数据 _mm_storeu_si128(dstSIMD, rgb0); srcSIMD++; dstSIMD = reinterpret_cast<__m128i*>(reinterpret_cast<unsigned char*>(dstSIMD)+12); } // 处理剩余不足4个的像素 for(; x < output.width; x++){ dstPtr[0] = srcPtr[0]; dstPtr[1] = srcPtr[1]; dstPtr[2] = srcPtr[2]; srcPtr +=4; dstPtr +=3; } srcPtr += skipBytes; }
2. 使用D3D11 Compute Shader硬件加速
将转换操作放到GPU执行,自动处理对齐并利用并行计算能力:
- 编写Compute Shader代码:
Texture2D<float4> inputTex : register(t0); RWTexture2D<float3> outputTex : register(u0); [numthreads(16,16,1)] void CS(uint3 dispatchThreadID : SV_DispatchThreadID) { float4 bgra = inputTex[dispatchThreadID.xy]; outputTex[dispatchThreadID.xy] = float3(bgra.b, bgra.g, bgra.r); }
- CPU端调度执行:
// 设置输入输出资源 context->CSSetShaderResources(0, 1, &srv); context->CSSetUnorderedAccessViews(0, 1, &uav, nullptr); // 计算线程组数量 UINT groupX = (output.width + 15) / 16; UINT groupY = (output.height + 15) / 16; context->Dispatch(groupX, groupY, 1); // 读取转换后的RGB数据 context->Map(outputTexture, 0, D3D11_MAP_READ, 0, &mappedOutput);
内容的提问来源于stack exchange,提问作者CommanderLake
相关产品推荐
相关产品推荐

