mirror of
https://github.com/Blinue/Magpie.git
synced 2026-06-24 02:04:10 +00:00
feat: 从文件加载纹理数据 (完成)
This commit is contained in:
parent
f3a707e021
commit
2ebd706052
2 changed files with 450 additions and 4 deletions
|
|
@ -1,5 +1,5 @@
|
|||
//!MAGPIE EFFECT
|
||||
//!VERSION 4
|
||||
//!VERSION 5
|
||||
//!CAPABILITY FP16
|
||||
|
||||
#include "../StubDefs.hlsli"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,458 @@
|
|||
#include "pch.h"
|
||||
#include "D3D12Context.h"
|
||||
#include "DDS.h"
|
||||
#include "Logger.h"
|
||||
#include "TextureHelper.h"
|
||||
#include "WICImageLoader.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 读取和保存 DDS 文件的代码来自 https://github.com/microsoft/DirectXTex
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
static winrt::com_ptr<ID3D12Resource> LoadTextureFormImage(
|
||||
static HRESULT LoadTextureDataFromFile(
|
||||
_In_z_ const wchar_t* fileName,
|
||||
std::unique_ptr<uint8_t[]>& ddsData,
|
||||
const DDS_HEADER** header,
|
||||
const uint8_t** bitData,
|
||||
size_t* bitSize
|
||||
) noexcept {
|
||||
if (!header || !bitData || !bitSize) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
*bitSize = 0;
|
||||
|
||||
// open the file
|
||||
wil::unique_hfile hFile(CreateFile2(
|
||||
fileName,
|
||||
GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING,
|
||||
nullptr
|
||||
));
|
||||
|
||||
if (!hFile) {
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
// Get the file size
|
||||
FILE_STANDARD_INFO fileInfo{};
|
||||
if (!GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo))) {
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
// File is too big for 32-bit allocation, so reject read
|
||||
if (fileInfo.EndOfFile.HighPart > 0) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Need at least enough data to fill the header and magic number to be a valid DDS
|
||||
if (fileInfo.EndOfFile.LowPart < DDS_MIN_HEADER_SIZE) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// create enough space for the file data
|
||||
ddsData.reset(new (std::nothrow) uint8_t[fileInfo.EndOfFile.LowPart]);
|
||||
if (!ddsData) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
// read the data in
|
||||
DWORD bytesRead = 0;
|
||||
if (!ReadFile(hFile.get(),
|
||||
ddsData.get(),
|
||||
fileInfo.EndOfFile.LowPart,
|
||||
&bytesRead,
|
||||
nullptr
|
||||
)) {
|
||||
ddsData.reset();
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
if (bytesRead < fileInfo.EndOfFile.LowPart) {
|
||||
ddsData.reset();
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
size_t len = fileInfo.EndOfFile.LowPart;
|
||||
|
||||
// DDS files always start with the same magic number ("DDS ")
|
||||
const auto dwMagicNumber = *reinterpret_cast<const uint32_t*>(ddsData.get());
|
||||
if (dwMagicNumber != DDS_MAGIC) {
|
||||
ddsData.reset();
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
auto hdr = reinterpret_cast<const DDS_HEADER*>(ddsData.get() + sizeof(uint32_t));
|
||||
|
||||
// Verify header to validate DDS file
|
||||
if (hdr->size != sizeof(DDS_HEADER) ||
|
||||
hdr->ddspf.size != sizeof(DDS_PIXELFORMAT)) {
|
||||
ddsData.reset();
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Check for DX10 extension
|
||||
bool bDXT10Header = false;
|
||||
if ((hdr->ddspf.flags & DDS_FOURCC) &&
|
||||
(MAKEFOURCC('D', 'X', '1', '0') == hdr->ddspf.fourCC)) {
|
||||
// Must be long enough for both headers and magic value
|
||||
if (len < DDS_DX10_HEADER_SIZE) {
|
||||
ddsData.reset();
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
bDXT10Header = true;
|
||||
}
|
||||
|
||||
// setup the pointers in the process request
|
||||
*header = hdr;
|
||||
auto offset = DDS_MIN_HEADER_SIZE
|
||||
+ (bDXT10Header ? sizeof(DDS_HEADER_DXT10) : 0u);
|
||||
*bitData = ddsData.get() + offset;
|
||||
*bitSize = len - offset;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static size_t BitsPerPixel(_In_ DXGI_FORMAT fmt) noexcept {
|
||||
switch (fmt) {
|
||||
case DXGI_FORMAT_R32G32B32A32_FLOAT:
|
||||
return 128;
|
||||
|
||||
case DXGI_FORMAT_R16G16B16A16_FLOAT:
|
||||
case DXGI_FORMAT_R16G16B16A16_UNORM:
|
||||
case DXGI_FORMAT_R16G16B16A16_SNORM:
|
||||
case DXGI_FORMAT_R32G32_FLOAT:
|
||||
return 64;
|
||||
|
||||
case DXGI_FORMAT_R10G10B10A2_UNORM:
|
||||
case DXGI_FORMAT_R11G11B10_FLOAT:
|
||||
case DXGI_FORMAT_R8G8B8A8_UNORM:
|
||||
case DXGI_FORMAT_R8G8B8A8_SNORM:
|
||||
case DXGI_FORMAT_R16G16_FLOAT:
|
||||
case DXGI_FORMAT_R16G16_UNORM:
|
||||
case DXGI_FORMAT_R16G16_SNORM:
|
||||
case DXGI_FORMAT_R32_FLOAT:
|
||||
return 32;
|
||||
|
||||
case DXGI_FORMAT_R8G8_UNORM:
|
||||
case DXGI_FORMAT_R8G8_SNORM:
|
||||
case DXGI_FORMAT_R16_FLOAT:
|
||||
case DXGI_FORMAT_R16_UNORM:
|
||||
case DXGI_FORMAT_R16_SNORM:
|
||||
return 16;
|
||||
|
||||
default:
|
||||
assert(fmt == DXGI_FORMAT_R8_UNORM || fmt == DXGI_FORMAT_R8_SNORM);
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
|
||||
#define ISBITMASK( r,g,b,a ) ( ddpf.RBitMask == r && ddpf.GBitMask == g && ddpf.BBitMask == b && ddpf.ABitMask == a )
|
||||
|
||||
static DXGI_FORMAT GetDXGIFormat(const DDS_PIXELFORMAT& ddpf) noexcept {
|
||||
if (ddpf.flags & DDS_RGB) {
|
||||
// Note that sRGB formats are written using the "DX10" extended header
|
||||
|
||||
switch (ddpf.RGBBitCount) {
|
||||
case 32:
|
||||
if (ISBITMASK(0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)) {
|
||||
return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
}
|
||||
|
||||
// No DXGI format maps to ISBITMASK(0x000000ff,0x0000ff00,0x00ff0000,0) aka D3DFMT_X8B8G8R8
|
||||
|
||||
// Note that many common DDS reader/writers (including D3DX) swap the
|
||||
// the RED/BLUE masks for 10:10:10:2 formats. We assume
|
||||
// below that the 'backwards' header mask is being used since it is most
|
||||
// likely written by D3DX. The more robust solution is to use the 'DX10'
|
||||
// header extension and specify the DXGI_FORMAT_R10G10B10A2_UNORM format directly
|
||||
|
||||
// For 'correct' writers, this should be 0x000003ff,0x000ffc00,0x3ff00000 for RGB data
|
||||
if (ISBITMASK(0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000)) {
|
||||
return DXGI_FORMAT_R10G10B10A2_UNORM;
|
||||
}
|
||||
|
||||
// No DXGI format maps to ISBITMASK(0x000003ff,0x000ffc00,0x3ff00000,0xc0000000) aka D3DFMT_A2R10G10B10
|
||||
|
||||
if (ISBITMASK(0x0000ffff, 0xffff0000, 0, 0)) {
|
||||
return DXGI_FORMAT_R16G16_UNORM;
|
||||
}
|
||||
|
||||
if (ISBITMASK(0xffffffff, 0, 0, 0)) {
|
||||
// Only 32-bit color channel format in D3D9 was R32F
|
||||
return DXGI_FORMAT_R32_FLOAT; // D3DX writes this out as a FourCC of 114
|
||||
}
|
||||
break;
|
||||
|
||||
case 24:
|
||||
// No 24bpp DXGI formats aka D3DFMT_R8G8B8
|
||||
break;
|
||||
|
||||
case 16:
|
||||
// No DXGI format maps to ISBITMASK(0x7c00,0x03e0,0x001f,0) aka D3DFMT_X1R5G5B5
|
||||
|
||||
// NVTT versions 1.x wrote this as RGB instead of LUMINANCE
|
||||
if (ISBITMASK(0x00ff, 0, 0, 0xff00)) {
|
||||
return DXGI_FORMAT_R8G8_UNORM;
|
||||
}
|
||||
if (ISBITMASK(0xffff, 0, 0, 0)) {
|
||||
return DXGI_FORMAT_R16_UNORM;
|
||||
}
|
||||
|
||||
// No DXGI format maps to ISBITMASK(0x0f00,0x00f0,0x000f,0) aka D3DFMT_X4R4G4B4
|
||||
|
||||
// No 3:3:2:8 or paletted DXGI formats aka D3DFMT_A8R3G3B2, D3DFMT_A8P8, etc.
|
||||
break;
|
||||
|
||||
case 8:
|
||||
// NVTT versions 1.x wrote this as RGB instead of LUMINANCE
|
||||
if (ISBITMASK(0xff, 0, 0, 0)) {
|
||||
return DXGI_FORMAT_R8_UNORM;
|
||||
}
|
||||
|
||||
// No 3:3:2 or paletted DXGI formats aka D3DFMT_R3G3B2, D3DFMT_P8
|
||||
break;
|
||||
|
||||
default:
|
||||
return DXGI_FORMAT_UNKNOWN;
|
||||
}
|
||||
} else if (ddpf.flags & DDS_LUMINANCE) {
|
||||
switch (ddpf.RGBBitCount) {
|
||||
case 16:
|
||||
if (ISBITMASK(0xffff, 0, 0, 0)) {
|
||||
return DXGI_FORMAT_R16_UNORM; // D3DX10/11 writes this out as DX10 extension
|
||||
}
|
||||
if (ISBITMASK(0x00ff, 0, 0, 0xff00)) {
|
||||
return DXGI_FORMAT_R8G8_UNORM; // D3DX10/11 writes this out as DX10 extension
|
||||
}
|
||||
break;
|
||||
|
||||
case 8:
|
||||
if (ISBITMASK(0xff, 0, 0, 0)) {
|
||||
return DXGI_FORMAT_R8_UNORM; // D3DX10/11 writes this out as DX10 extension
|
||||
}
|
||||
|
||||
// No DXGI format maps to ISBITMASK(0x0f,0,0,0xf0) aka D3DFMT_A4L4
|
||||
|
||||
if (ISBITMASK(0x00ff, 0, 0, 0xff00)) {
|
||||
return DXGI_FORMAT_R8G8_UNORM; // Some DDS writers assume the bitcount should be 8 instead of 16
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return DXGI_FORMAT_UNKNOWN;
|
||||
}
|
||||
} else if (ddpf.flags & DDS_BUMPDUDV) {
|
||||
switch (ddpf.RGBBitCount) {
|
||||
case 32:
|
||||
if (ISBITMASK(0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)) {
|
||||
return DXGI_FORMAT_R8G8B8A8_SNORM; // D3DX10/11 writes this out as DX10 extension
|
||||
}
|
||||
if (ISBITMASK(0x0000ffff, 0xffff0000, 0, 0)) {
|
||||
return DXGI_FORMAT_R16G16_SNORM; // D3DX10/11 writes this out as DX10 extension
|
||||
}
|
||||
|
||||
// No DXGI format maps to ISBITMASK(0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000) aka D3DFMT_A2W10V10U10
|
||||
break;
|
||||
|
||||
case 16:
|
||||
if (ISBITMASK(0x00ff, 0xff00, 0, 0)) {
|
||||
return DXGI_FORMAT_R8G8_SNORM; // D3DX10/11 writes this out as DX10 extension
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return DXGI_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
// No DXGI format maps to DDPF_BUMPLUMINANCE aka D3DFMT_L6V5U5, D3DFMT_X8L8V8U8
|
||||
} else if (ddpf.flags & DDS_FOURCC) {
|
||||
// Check for D3DFORMAT enums being set here
|
||||
switch (ddpf.fourCC) {
|
||||
case 36: // D3DFMT_A16B16G16R16
|
||||
return DXGI_FORMAT_R16G16B16A16_UNORM;
|
||||
|
||||
case 110: // D3DFMT_Q16W16V16U16
|
||||
return DXGI_FORMAT_R16G16B16A16_SNORM;
|
||||
|
||||
case 111: // D3DFMT_R16F
|
||||
return DXGI_FORMAT_R16_FLOAT;
|
||||
|
||||
case 112: // D3DFMT_G16R16F
|
||||
return DXGI_FORMAT_R16G16_FLOAT;
|
||||
|
||||
case 113: // D3DFMT_A16B16G16R16F
|
||||
return DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
|
||||
case 114: // D3DFMT_R32F
|
||||
return DXGI_FORMAT_R32_FLOAT;
|
||||
|
||||
case 115: // D3DFMT_G32R32F
|
||||
return DXGI_FORMAT_R32G32_FLOAT;
|
||||
|
||||
case 116: // D3DFMT_A32B32G32R32F
|
||||
return DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
|
||||
// No DXGI format maps to D3DFMT_CxV8U8
|
||||
|
||||
default:
|
||||
return DXGI_FORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
return DXGI_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
#undef ISBITMASK
|
||||
|
||||
static bool CheckDDSHeader(DXGI_FORMAT format, _In_ const DDS_HEADER* header) noexcept {
|
||||
// Bound sizes (for security purposes we don't trust DDS file metadata larger than the Direct3D hardware requirements)
|
||||
if (header->mipMapCount > D3D12_REQ_MIP_LEVELS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((header->ddspf.flags & DDS_FOURCC) &&
|
||||
(MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC)) {
|
||||
auto d3d10ext = (const DDS_HEADER_DXT10*)((const char*)header + sizeof(DDS_HEADER));
|
||||
|
||||
if (d3d10ext->arraySize != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (d3d10ext->dxgiFormat != format) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (d3d10ext->resourceDimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (d3d10ext->miscFlag & DDS_RESOURCE_MISC_TEXTURECUBE) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (format != GetDXGIFormat(header->ddspf)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header->flags & DDS_HEADER_FLAGS_VOLUME) {
|
||||
return false;
|
||||
} else if (header->caps2 & DDS_CUBEMAP) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (header->width > D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION ||
|
||||
header->height > D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static winrt::com_ptr<ID3D12Resource> LoadTextureFromDDS(
|
||||
const wchar_t* fileName,
|
||||
DXGI_FORMAT format,
|
||||
const D3D12Context& d3d12Context,
|
||||
SizeU& textureSize
|
||||
) noexcept {
|
||||
const DDS_HEADER* header = nullptr;
|
||||
const uint8_t* bitData = nullptr;
|
||||
size_t bitSize = 0;
|
||||
|
||||
std::unique_ptr<uint8_t[]> ddsData;
|
||||
HRESULT hr = LoadTextureDataFromFile(
|
||||
fileName,
|
||||
ddsData,
|
||||
&header,
|
||||
&bitData,
|
||||
&bitSize
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("LoadTextureDataFromFile 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!CheckDDSHeader(format, header)) {
|
||||
Logger::Get().Error("CheckDDSHeader 失败");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
textureSize.width = header->width;
|
||||
textureSize.height = header->height;
|
||||
|
||||
size_t bpp = BitsPerPixel(format);
|
||||
size_t rowPitch = (uint64_t(textureSize.width) * bpp + 7u) / 8u; // round up to nearest byte
|
||||
size_t numBytes = rowPitch * textureSize.height;
|
||||
|
||||
if (numBytes > bitSize) {
|
||||
Logger::Get().Error("文件格式错误");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
winrt::com_ptr<ID3D12Resource> result;
|
||||
|
||||
ID3D12Device5* device = d3d12Context.GetDevice();
|
||||
|
||||
CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_UPLOAD);
|
||||
|
||||
D3D12_HEAP_FLAGS heapFlags = d3d12Context.IsHeapFlagCreateNotZeroedSupported() ?
|
||||
D3D12_HEAP_FLAG_CREATE_NOT_ZEROED : D3D12_HEAP_FLAG_NONE;
|
||||
|
||||
CD3DX12_RESOURCE_DESC texDesc = CD3DX12_RESOURCE_DESC::Tex2D(
|
||||
format, textureSize.width, textureSize.height, 1, 1);
|
||||
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT textureLayout;
|
||||
UINT64 bufferSize;
|
||||
device->GetCopyableFootprints(&texDesc, 0, 1, 0,
|
||||
&textureLayout, nullptr, nullptr, &bufferSize);
|
||||
|
||||
CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(bufferSize);
|
||||
|
||||
hr = device->CreateCommittedResource(
|
||||
&heapProps,
|
||||
heapFlags,
|
||||
&bufferDesc,
|
||||
D3D12_RESOURCE_STATE_GENERIC_READ,
|
||||
nullptr,
|
||||
IID_PPV_ARGS(&result)
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateCommittedResource 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CD3DX12_RANGE emptyRange{};
|
||||
void* pData;
|
||||
hr = result->Map(0, &emptyRange, &pData);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("ID3D12Resource::Map 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (rowPitch == textureLayout.Footprint.RowPitch) {
|
||||
assert(numBytes <= bufferSize);
|
||||
std::memcpy(pData, bitData, numBytes);
|
||||
} else {
|
||||
for (uint32_t i = 0; i < textureSize.height; ++i) {
|
||||
std::memcpy(
|
||||
(uint8_t*)pData + textureLayout.Footprint.RowPitch * i,
|
||||
bitData + rowPitch * i,
|
||||
rowPitch
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
result->Unmap(0, nullptr);
|
||||
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
static winrt::com_ptr<ID3D12Resource> LoadTextureFromImage(
|
||||
const wchar_t* fileName,
|
||||
const D3D12Context& d3d12Context,
|
||||
DXGI_FORMAT& format,
|
||||
|
|
@ -115,11 +561,11 @@ winrt::com_ptr<ID3D12Resource> TextureHelper::LoadFromFile(
|
|||
SizeU& textureSize
|
||||
) noexcept {
|
||||
if (fileName.ends_with(L".dds")) {
|
||||
return nullptr;
|
||||
return LoadTextureFromDDS(fileName.c_str(), format, d3d12Context, textureSize);
|
||||
} else {
|
||||
assert(format == DXGI_FORMAT_R8G8B8A8_UNORM || format == DXGI_FORMAT_R16G16B16A16_FLOAT ||
|
||||
format == DXGI_FORMAT_R32G32B32A32_FLOAT);
|
||||
return LoadTextureFormImage(fileName.c_str(), d3d12Context, format, textureSize);
|
||||
return LoadTextureFromImage(fileName.c_str(), d3d12Context, format, textureSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue