feat: 从文件加载纹理数据 (p2)

This commit is contained in:
Xu 2026-04-08 14:43:08 +08:00
commit f3a707e021
9 changed files with 231 additions and 105 deletions

View file

@ -56,6 +56,7 @@
<ClInclude Include="CursorDrawer.h" />
<ClInclude Include="CursorManager.h" />
<ClInclude Include="DDS.h" />
<ClInclude Include="include\WICImageLoader.h" />
<ClInclude Include="TextureHelper.h" />
<ClInclude Include="DirtyRectsOptimizer.h" />
<ClInclude Include="DuplicateFrameChecker.h" />
@ -119,6 +120,7 @@
<ClCompile Include="SrcTracker.cpp" />
<ClCompile Include="StepTimer.cpp" />
<ClCompile Include="SwapChainPresenter.cpp" />
<ClCompile Include="WICImageLoader.cpp" />
<ClCompile Include="Win32Helper.cpp" />
<ClCompile Include="WindowHelper.cpp" />
</ItemGroup>

View file

@ -129,6 +129,9 @@
<ClInclude Include="TextureHelper.h">
<Filter>Helpers</Filter>
</ClInclude>
<ClInclude Include="include\WICImageLoader.h">
<Filter>Include</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ScalingRuntime.cpp" />
@ -204,6 +207,7 @@
<ClCompile Include="TextureHelper.cpp">
<Filter>Helpers</Filter>
</ClCompile>
<ClCompile Include="WICImageLoader.cpp" />
</ItemGroup>
<ItemGroup>
<FxCompile Include="shaders\MaskedCursorPS.hlsl">

View file

@ -776,10 +776,11 @@ HRESULT ShaderEffectDrawer::_CreateTextures() noexcept {
std::string_view(_effectInfo->name.c_str(), delimPos + 1), effectTexDesc.source);
_TextureSourceData& sourceData = _textureSourceDatas.emplace_back();
// 可能会把 dxgiFormat 修改为 sRGB
sourceData.uploadBuffer = TextureHelper::LoadFromFile(
StrHelper::UTF8ToUTF16(texPath),
dxgiFormat,
*_d3d12Context,
dxgiFormat,
sourceData.textureSize
);
if (!sourceData.uploadBuffer) {

View file

@ -850,7 +850,7 @@ static bool ResolveTextureFormat(
// UNKNOWN 不可用
constexpr size_t formatCount = std::size(SHADER_TEXTURE_FORMAT_PROPS) - 1;
result.reserve(formatCount);
for (size_t i = 1; i < formatCount; ++i) {
for (size_t i = 1; i < std::size(SHADER_TEXTURE_FORMAT_PROPS); ++i) {
result.emplace(SHADER_TEXTURE_FORMAT_PROPS[i].name, (ShaderEffectTextureFormat)i);
}
return result;
@ -948,12 +948,10 @@ static bool ResolveTextureBlock(
// 从图片加载纹理时格式存在限制
if (!desc.source.ends_with(".dds") &&
desc.format != ShaderEffectTextureFormat::R8G8B8A8_UNORM &&
desc.format != ShaderEffectTextureFormat::R10G10B10A2_UNORM &&
desc.format != ShaderEffectTextureFormat::R16G16B16A16_UNORM &&
desc.format != ShaderEffectTextureFormat::R16G16B16A16_FLOAT &&
desc.format != ShaderEffectTextureFormat::R32G32B32A32_FLOAT)
{
state.errorMsg = "从图片加载纹理时格式只能是 R8G8B8A8_UNORM、R10G10B10A2_UNORM、R16G16B16A16_UNORM、R16G16B16A16_FLOAT 或 R32G32B32A32_FLOAT";
state.errorMsg = "从图片加载纹理时格式只支持 R8G8B8A8_UNORM、R16G16B16A16_FLOAT 和 R32G32B32A32_FLOAT";
return false;
}
}

View file

@ -2,58 +2,21 @@
#include "D3D12Context.h"
#include "Logger.h"
#include "TextureHelper.h"
#include <wincodec.h>
#include "WICImageLoader.h"
namespace Magpie {
static winrt::com_ptr<ID3D12Resource> LoadTextureFormImage(
const wchar_t* fileName,
DXGI_FORMAT format,
const D3D12Context& d3d12Context,
DXGI_FORMAT& format,
SizeU& textureSize
) noexcept {
winrt::com_ptr<IWICImagingFactory2> wicImgFactory =
winrt::try_create_instance<IWICImagingFactory2>(CLSID_WICImagingFactory);
if (!wicImgFactory) {
Logger::Get().Error("创建 WICImagingFactory 失败");
return nullptr;
}
// 读取图像文件
winrt::com_ptr<IWICBitmapDecoder> decoder;
HRESULT hr = wicImgFactory->CreateDecoderFromFilename(
fileName, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder.put());
if (FAILED(hr)) {
Logger::Get().ComError("IWICImagingFactory::CreateDecoderFromFilename 失败", hr);
return nullptr;
}
winrt::com_ptr<IWICBitmapFrameDecode> frame;
hr = decoder->GetFrame(0, frame.put());
if (FAILED(hr)) {
Logger::Get().ComError("IWICBitmapDecoder::GetFrame 失败", hr);
return nullptr;
}
// 转换格式
winrt::com_ptr<IWICFormatConverter> formatConverter;
hr = wicImgFactory->CreateFormatConverter(formatConverter.put());
if (FAILED(hr)) {
Logger::Get().ComError("IWICImagingFactory::CreateFormatConverter 失败", hr);
return nullptr;
}
WICPixelFormatGUID targetWicFormat;
switch (format) {
case DXGI_FORMAT_R8G8B8A8_UNORM:
targetWicFormat = GUID_WICPixelFormat32bppRGBA;
break;
case DXGI_FORMAT_R10G10B10A2_UNORM:
targetWicFormat = GUID_WICPixelFormat32bppR10G10B10A2;
break;
case DXGI_FORMAT_R16G16B16A16_UNORM:
targetWicFormat = GUID_WICPixelFormat64bppRGBA;
break;
case DXGI_FORMAT_R16G16B16A16_FLOAT:
targetWicFormat = GUID_WICPixelFormat64bppRGBAHalf;
break;
@ -62,16 +25,22 @@ static winrt::com_ptr<ID3D12Resource> LoadTextureFormImage(
targetWicFormat = GUID_WICPixelFormat128bppRGBAFloat;
break;
}
hr = formatConverter->Initialize(frame.get(), targetWicFormat,
WICBitmapDitherTypeNone, nullptr, 0, WICBitmapPaletteTypeCustom);
if (FAILED(hr)) {
Logger::Get().ComError("IWICFormatConverter::Initialize 失败", hr);
bool isSRGB = false;
winrt::com_ptr<IWICBitmapSource> wicBitmap =
WICImageLoader::LoadFromFile(fileName, targetWicFormat, isSRGB);
if (!wicBitmap) {
Logger::Get().Error("WICImageLoader::LoadFromFile 失败");
return nullptr;
}
// 如果图片是 sRGB 则修改 format。对于其他格式 WIC 会自动执行伽马校正。
if (isSRGB) {
format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
}
// 检查 D3D 纹理尺寸限制
hr = formatConverter->GetSize(&textureSize.width, &textureSize.height);
HRESULT hr = wicBitmap->GetSize(&textureSize.width, &textureSize.height);
if (FAILED(hr)) {
Logger::Get().ComError("GetSize 失败", hr);
return nullptr;
@ -101,6 +70,9 @@ static winrt::com_ptr<ID3D12Resource> LoadTextureFormImage(
device->GetCopyableFootprints(&texDesc, 0, 1, 0,
&textureLayout, nullptr, nullptr, &bufferSize);
// GetCopyableFootprints 计算出的字节数可能不包含最后一行的填充位,而 WIC 需要
bufferSize = std::max(bufferSize, UINT64(textureLayout.Footprint.RowPitch * textureSize.height));
CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(bufferSize);
hr = device->CreateCommittedResource(
@ -125,8 +97,7 @@ static winrt::com_ptr<ID3D12Resource> LoadTextureFormImage(
}
// 这个调用是否会意外读取 pData 的内容?保险起见可以使用临时缓冲区。
hr = formatConverter->CopyPixels(
nullptr, textureLayout.Footprint.RowPitch, (UINT)bufferSize, (BYTE*)pData);
hr = wicBitmap->CopyPixels(nullptr, textureLayout.Footprint.RowPitch, (UINT)bufferSize, (BYTE*)pData);
if (FAILED(hr)) {
Logger::Get().ComError("CopyPixels 失败", hr);
return nullptr;
@ -139,17 +110,16 @@ static winrt::com_ptr<ID3D12Resource> LoadTextureFormImage(
winrt::com_ptr<ID3D12Resource> TextureHelper::LoadFromFile(
wil::zwstring_view fileName,
DXGI_FORMAT format,
const D3D12Context& d3d12Context,
DXGI_FORMAT& format,
SizeU& textureSize
) noexcept {
if (fileName.ends_with(L".dds")) {
return nullptr;
} else {
assert(format == DXGI_FORMAT_R8G8B8A8_UNORM || format == DXGI_FORMAT_R10G10B10A2_UNORM ||
format == DXGI_FORMAT_R16G16B16A16_UNORM || format == DXGI_FORMAT_R16G16B16A16_FLOAT ||
assert(format == DXGI_FORMAT_R8G8B8A8_UNORM || format == DXGI_FORMAT_R16G16B16A16_FLOAT ||
format == DXGI_FORMAT_R32G32B32A32_FLOAT);
return LoadTextureFormImage(fileName.c_str(), format, d3d12Context, textureSize);
return LoadTextureFormImage(fileName.c_str(), d3d12Context, format, textureSize);
}
}

View file

@ -5,11 +5,11 @@ namespace Magpie {
class D3D12Context;
struct TextureHelper {
// 支持 dds、bmp、jpg、png 和 tif。从图片加载纹理时不会执行伽马校正
// 支持 dds、bmp、jpg、png 和 tif。自动检测 sRGB 并修改 format
static winrt::com_ptr<ID3D12Resource> LoadFromFile(
wil::zwstring_view fileName,
DXGI_FORMAT format,
const D3D12Context& d3d12Context,
DXGI_FORMAT& format,
SizeU& textureSize
) noexcept;
};

View file

@ -0,0 +1,164 @@
#include "pch.h"
#include "WICImageLoader.h"
#include "Logger.h"
#include <propkey.h>
namespace Magpie {
static void QueryExifInfos(IWICBitmapFrameDecode* frame, bool& isSRGB, uint16_t& orientation) noexcept {
winrt::com_ptr<IWICMetadataQueryReader> metaReader;
HRESULT hr = frame->GetMetadataQueryReader(metaReader.put());
if (FAILED(hr)) {
Logger::Get().ComError("IWICBitmapFrameDecode::GetMetadataQueryReader 失败", hr);
return;
}
GUID containerFormat;
hr = metaReader->GetContainerFormat(&containerFormat);
if (FAILED(hr)) {
Logger::Get().ComError("IWICMetadataQueryReader::GetContainerFormat 失败", hr);
return;
}
wil::unique_prop_variant value;
// 检索色域的代码参考自
// https://github.com/microsoft/DirectXTK12/blob/3fdbcb5a6b73994a52c0b0d7a0c5743287004dad/Src/WICTextureLoader.cpp#L350-L417
if (containerFormat == GUID_ContainerFormatPng) {
if (SUCCEEDED(metaReader->GetMetadataByName(L"/sRGB/RenderingIntent", &value)) && value.vt == VT_UI1) {
isSRGB = true;
} else if (SUCCEEDED(metaReader->GetMetadataByName(L"/gAMA/ImageGamma", &value)) && value.vt == VT_UI4) {
isSRGB = value.uintVal == 45455;
}
} else {
if (SUCCEEDED(metaReader->GetMetadataByName(L"System.Image.ColorSpace", &value)) && value.vt == VT_UI2) {
isSRGB = value.uiVal == 1;
}
}
// 检索旋转信息
if (value.vt != VT_EMPTY) {
PropVariantClear(&value);
}
if (SUCCEEDED(metaReader->GetMetadataByName(L"System.Photo.Orientation", &value)) && value.vt == VT_UI2) {
orientation = value.uiVal;
}
}
winrt::com_ptr<IWICBitmapSource> WICImageLoader::LoadFromFile(
const wchar_t* fileName,
WICPixelFormatGUID targetFormat,
bool& isSRGB
) noexcept {
winrt::com_ptr<IWICImagingFactory2> wicImgFactory =
winrt::try_create_instance<IWICImagingFactory2>(CLSID_WICImagingFactory);
if (!wicImgFactory) {
Logger::Get().Error("创建 WICImagingFactory 失败");
return nullptr;
}
// 读取图像文件
winrt::com_ptr<IWICBitmapDecoder> decoder;
HRESULT hr = wicImgFactory->CreateDecoderFromFilename(
fileName, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder.put());
if (FAILED(hr)) {
Logger::Get().ComError("IWICImagingFactory::CreateDecoderFromFilename 失败", hr);
return nullptr;
}
winrt::com_ptr<IWICBitmapFrameDecode> frame;
hr = decoder->GetFrame(0, frame.put());
if (FAILED(hr)) {
Logger::Get().ComError("IWICBitmapDecoder::GetFrame 失败", hr);
return nullptr;
}
// 默认视为 sRGB
isSRGB = true;
uint16_t orientation = PHOTO_ORIENTATION_NORMAL;
QueryExifInfos(frame.get(), isSRGB, orientation);
winrt::com_ptr<IWICBitmapSource> source = std::move(frame);
// 处理旋转
if (orientation >= 2 && orientation <= 8) {
// 来自 https://github.com/microsoft/Win2D/blob/25680382dd2136779e10ea6084f0c5ba437ae288/winrt/lib/images/CanvasBitmap.cpp#L209
WICBitmapTransformOptions transformOptions;
switch (orientation) {
case PHOTO_ORIENTATION_ROTATE90:
transformOptions = WICBitmapTransformRotate270;
break;
case PHOTO_ORIENTATION_ROTATE180:
transformOptions = WICBitmapTransformRotate180;
break;
case PHOTO_ORIENTATION_ROTATE270:
transformOptions = WICBitmapTransformRotate90;
break;
case PHOTO_ORIENTATION_FLIPHORIZONTAL:
transformOptions = WICBitmapTransformFlipHorizontal;
break;
case PHOTO_ORIENTATION_FLIPVERTICAL:
transformOptions = WICBitmapTransformFlipVertical;
break;
case PHOTO_ORIENTATION_TRANSPOSE:
transformOptions = WICBitmapTransformOptions(WICBitmapTransformRotate270 | WICBitmapTransformFlipHorizontal);
break;
default:
transformOptions = WICBitmapTransformOptions(WICBitmapTransformRotate90 | WICBitmapTransformFlipHorizontal);
break;
}
// 先将图片数据读到内存,否则旋转会非常慢
winrt::com_ptr<IWICBitmap> memBitmap;
hr = wicImgFactory->CreateBitmapFromSource(source.get(), WICBitmapCacheOnDemand, memBitmap.put());
if (FAILED(hr)) {
Logger::Get().ComError("IWICImagingFactory::CreateBitmapFromSource 失败", hr);
return nullptr;
}
winrt::com_ptr<IWICBitmapFlipRotator> flipRotator;
hr = wicImgFactory->CreateBitmapFlipRotator(flipRotator.put());
if (FAILED(hr)) {
Logger::Get().ComError("IWICImagingFactory::CreateBitmapFlipRotator 失败", hr);
return nullptr;
}
hr = flipRotator->Initialize(memBitmap.get(), transformOptions);
if (FAILED(hr)) {
Logger::Get().ComError("IWICBitmapFlipRotator::Initialize 失败", hr);
return nullptr;
}
source = std::move(flipRotator);
}
WICPixelFormatGUID sourceWicFormat;
hr = source->GetPixelFormat(&sourceWicFormat);
if (FAILED(hr)) {
Logger::Get().ComError("IWICBitmapSource::GetPixelFormat 失败", hr);
return nullptr;
}
if (sourceWicFormat == targetFormat) {
return std::move(source);
}
// 转换格式
winrt::com_ptr<IWICFormatConverter> formatConverter;
hr = wicImgFactory->CreateFormatConverter(formatConverter.put());
if (FAILED(hr)) {
Logger::Get().ComError("IWICImagingFactory::CreateFormatConverter 失败", hr);
return nullptr;
}
hr = formatConverter->Initialize(source.get(), targetFormat,
WICBitmapDitherTypeNone, nullptr, 0, WICBitmapPaletteTypeCustom);
if (FAILED(hr)) {
Logger::Get().ComError("IWICFormatConverter::Initialize 失败", hr);
return nullptr;
}
return std::move(formatConverter);
}
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <wincodec.h>
namespace Magpie {
struct WICImageLoader {
static winrt::com_ptr<IWICBitmapSource> LoadFromFile(
const wchar_t* fileName,
WICPixelFormatGUID targetFormat,
bool& isSRGB
) noexcept;
};
}

View file

@ -1,7 +1,9 @@
#include "pch.h"
#include "AppXReader.h"
#include "ByteBuffer.h"
#include "Logger.h"
#include "StrHelper.h"
#include "WICImageLoader.h"
#include "Win32Helper.h"
#include <appmodel.h>
#include <AppxPackaging.h>
@ -481,45 +483,16 @@ private:
// 如果图标和背景的对比度太低,使用主题色填充背景
static SoftwareBitmap AutoFillBackground(const std::wstring& iconPath, bool isLightTheme, bool noPath) {
com_ptr<IWICImagingFactory2> wicImgFactory = try_create_instance<IWICImagingFactory2>(CLSID_WICImagingFactory);
if (!wicImgFactory) {
Logger::Get().Error("创建 WICImagingFactory2 失败");
bool isSRGB = false;
winrt::com_ptr<IWICBitmapSource> wicBitmap =
WICImageLoader::LoadFromFile(iconPath.c_str(), GUID_WICPixelFormat32bppBGRA, isSRGB);
if (!wicBitmap) {
Logger::Get().Error("WICImageLoader::LoadFromFile 失败");
return nullptr;
}
// 读取图像文件
winrt::com_ptr<IWICBitmapDecoder> decoder;
HRESULT hr = wicImgFactory->CreateDecoderFromFilename(
iconPath.c_str(), nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder.put());
if (FAILED(hr)) {
Logger::Get().ComError("CreateDecoderFromFilename 失败", hr);
return nullptr;
}
winrt::com_ptr<IWICBitmapFrameDecode> frame;
hr = decoder->GetFrame(0, frame.put());
if (FAILED(hr)) {
Logger::Get().ComError("IWICBitmapFrameDecode::GetFrame 失败", hr);
return nullptr;
}
// 转换格式
winrt::com_ptr<IWICFormatConverter> formatConverter;
hr = wicImgFactory->CreateFormatConverter(formatConverter.put());
if (FAILED(hr)) {
Logger::Get().ComError("CreateFormatConverter 失败", hr);
return nullptr;
}
hr = formatConverter->Initialize(frame.get(),
GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, nullptr, 0, WICBitmapPaletteTypeCustom);
if (FAILED(hr)) {
Logger::Get().ComError("IWICFormatConverter::Initialize 失败", hr);
return nullptr;
}
UINT width, height;
hr = formatConverter->GetSize(&width, &height);
HRESULT hr = wicBitmap->GetSize(&width, &height);
if (FAILED(hr)) {
Logger::Get().ComError("GetSize 失败", hr);
return nullptr;
@ -527,9 +500,9 @@ static SoftwareBitmap AutoFillBackground(const std::wstring& iconPath, bool isLi
UINT stride = width * 4;
UINT size = stride * height;
std::unique_ptr<uint8_t[]> buf(new uint8_t[size]);
ByteBuffer buf(size);
hr = formatConverter->CopyPixels(nullptr, stride, size, buf.get());
hr = wicBitmap->CopyPixels(nullptr, stride, size, buf.Data());
if (FAILED(hr)) {
Logger::Get().ComError("CopyPixels 失败", hr);
return nullptr;
@ -539,13 +512,14 @@ static SoftwareBitmap AutoFillBackground(const std::wstring& iconPath, bool isLi
float lumaTotal = 0;
uint32_t lumaCount = 0;
for (uint32_t i = 0, len = width * height; i < len; ++i) {
uint8_t* pixel = &buf.get()[i * 4];
uint8_t* pixel = &buf[i * 4];
uint8_t alpha = pixel[3];
if (alpha == 0) {
continue;
}
// 精度要求较低,因此不进行伽马校正
float luma = 0.299f * pixel[0] + 0.587f * pixel[1] + 0.114f * pixel[2];
if (alpha != 255) {
float alphaNorm = alpha / 255.0f;
@ -567,15 +541,14 @@ static SoftwareBitmap AutoFillBackground(const std::wstring& iconPath, bool isLi
BitmapBuffer buffer = bitmap.LockBuffer(BitmapBufferAccessMode::Write);
uint8_t* pixels = buffer.CreateReference().data();
const uint8_t* origin = buf.get();
for (size_t i = 0, pixelsSize = static_cast<size_t>(width) * height * 4; i < pixelsSize; i += 4) {
for (uint32_t i = 0, pixelsSize = width * height * 4; i < pixelsSize; i += 4) {
// 预乘 Alpha 通道
const float alpha = origin[i + 3] / 255.0f;
const float alpha = buf[i + 3] / 255.0f;
pixels[i] = (BYTE)std::lround(origin[i] * alpha);
pixels[i + 1] = (BYTE)std::lround(origin[i + 1] * alpha);
pixels[i + 2] = (BYTE)std::lround(origin[i + 2] * alpha);
pixels[i + 3] = origin[i + 3];
pixels[i] = (BYTE)std::lround(buf[i] * alpha);
pixels[i + 1] = (BYTE)std::lround(buf[i + 1] * alpha);
pixels[i + 2] = (BYTE)std::lround(buf[i + 2] * alpha);
pixels[i + 3] = buf[i + 3];
}
}
return bitmap;
@ -598,7 +571,7 @@ static SoftwareBitmap AutoFillBackground(const std::wstring& iconPath, bool isLi
std::fill_n((uint32_t*)pixels, totalWidth * totalHeight, fillColor);
pixels += (borderHeight * totalWidth + borderWidth) * 4;
const uint8_t* origin = buf.get();
const uint8_t* origin = buf.Data();
for (UINT i = 0; i < height; ++i) {
for (UINT j = 0; j < width; ++j, origin += 4, pixels += 4) {
const float alpha = origin[3] / 255.0f;