feat: 新版 FX 解析器 (p1)

This commit is contained in:
Xu 2026-03-21 14:17:22 +08:00
commit 9ac3c6d11d
14 changed files with 228 additions and 348 deletions

View file

@ -1,75 +0,0 @@
#include "pch.h"
#include "BackendDescriptorStore.h"
#include "Logger.h"
namespace Magpie {
ID3D11ShaderResourceView* BackendDescriptorStore::GetShaderResourceView(ID3D11Texture2D* texture) noexcept {
if (auto it = _srvMap.find(texture); it != _srvMap.end()) {
return it->second.get();
}
winrt::com_ptr<ID3D11ShaderResourceView> srv;
HRESULT hr = _d3dDevice->CreateShaderResourceView(texture, nullptr, srv.put());
if (FAILED(hr)) {
Logger::Get().ComError("CreateShaderResourceView 失败", hr);
return nullptr;
}
return _srvMap.emplace(texture, std::move(srv)).first->second.get();
}
ID3D11UnorderedAccessView* BackendDescriptorStore::GetUnorderedAccessView(ID3D11Texture2D* texture) noexcept {
if (auto it = _uavMap.find(texture); it != _uavMap.end()) {
return it->second.get();
}
winrt::com_ptr<ID3D11UnorderedAccessView> uav;
D3D11_UNORDERED_ACCESS_VIEW_DESC desc{
.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D
};
HRESULT hr = _d3dDevice->CreateUnorderedAccessView(texture, &desc, uav.put());
if (FAILED(hr)) {
Logger::Get().ComError("CreateUnorderedAccessView 失败", hr);
return nullptr;
}
return _uavMap.emplace(texture, std::move(uav)).first->second.get();
}
ID3D11UnorderedAccessView* BackendDescriptorStore::GetUnorderedAccessView(
ID3D11Buffer* buffer,
uint32_t numElements,
DXGI_FORMAT format
) noexcept {
if (auto it = _uavMap.find(buffer); it != _uavMap.end()) {
return it->second.get();
}
winrt::com_ptr<ID3D11UnorderedAccessView> uav;
D3D11_UNORDERED_ACCESS_VIEW_DESC desc{
.Format = format,
.ViewDimension = D3D11_UAV_DIMENSION_BUFFER,
.Buffer{
.NumElements = numElements
}
};
HRESULT hr = _d3dDevice->CreateUnorderedAccessView(buffer, &desc, uav.put());
if (FAILED(hr)) {
Logger::Get().ComError("CreateUnorderedAccessView 失败", hr);
return nullptr;
}
return _uavMap.emplace(buffer, std::move(uav)).first->second.get();
}
void BackendDescriptorStore::RemoveCache(ID3D11Texture2D* texture) noexcept {
_srvMap.erase(texture);
_uavMap.erase(texture);
}
}

View file

@ -1,35 +0,0 @@
#pragma once
#include <parallel_hashmap/phmap.h>
namespace Magpie {
class BackendDescriptorStore {
public:
BackendDescriptorStore() = default;
BackendDescriptorStore(const BackendDescriptorStore&) = delete;
BackendDescriptorStore(BackendDescriptorStore&&) = default;
void Initialize(ID3D11Device5* d3dDevice) noexcept {
_d3dDevice = d3dDevice;
}
ID3D11ShaderResourceView* GetShaderResourceView(ID3D11Texture2D* texture) noexcept;
ID3D11UnorderedAccessView* GetUnorderedAccessView(ID3D11Texture2D* texture) noexcept;
ID3D11UnorderedAccessView* GetUnorderedAccessView(
ID3D11Buffer* buffer,
uint32_t numElements,
DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN
) noexcept;
void RemoveCache(ID3D11Texture2D* texture) noexcept;
private:
ID3D11Device5* _d3dDevice = nullptr;
phmap::flat_hash_map<ID3D11Texture2D*, winrt::com_ptr<ID3D11ShaderResourceView>> _srvMap;
phmap::flat_hash_map<void*, winrt::com_ptr<ID3D11UnorderedAccessView>> _uavMap;
};
}

View file

@ -1,5 +1,6 @@
#pragma once
#include "SmallVector.h"
#include <d3d11_4.h>
namespace Magpie {

View file

@ -1,124 +0,0 @@
#include "pch.h"
#include "EffectsProfiler.h"
namespace Magpie {
void EffectsProfiler::Start(ID3D11Device* d3dDevice, uint32_t passCount) noexcept {
assert(!IsProfiling() && passCount > 0);
_passQueries.resize(passCount);
D3D11_QUERY_DESC desc{ .Query = D3D11_QUERY_TIMESTAMP_DISJOINT };
d3dDevice->CreateQuery(&desc, _disjointQuery.put());
desc.Query = D3D11_QUERY_TIMESTAMP;
d3dDevice->CreateQuery(&desc, _startQuery.put());
for (winrt::com_ptr<ID3D11Query>& query : _passQueries) {
d3dDevice->CreateQuery(&desc, query.put());
}
}
void EffectsProfiler::Stop() noexcept {
_disjointQuery = nullptr;
_startQuery = nullptr;
_passQueries.clear();
}
bool EffectsProfiler::IsProfiling() const noexcept {
return (bool)_disjointQuery;
}
void EffectsProfiler::SetPassCount(ID3D11Device* d3dDevice, uint32_t passCount) noexcept {
if (!IsProfiling()) {
return;
}
assert(passCount > 0);
const uint32_t oldPassCount = (uint32_t)_passQueries.size();
if (passCount == oldPassCount) {
return;
}
_passQueries.resize(passCount);
if (passCount > oldPassCount) {
D3D11_QUERY_DESC desc{ .Query = D3D11_QUERY_TIMESTAMP };
for (uint32_t i = oldPassCount; i < passCount; ++i) {
d3dDevice->CreateQuery(&desc, _passQueries[i].put());
}
}
}
void EffectsProfiler::OnBeginEffects(ID3D11DeviceContext* d3dDC) noexcept {
if (!IsProfiling()) {
return;
}
d3dDC->Begin(_disjointQuery.get());
d3dDC->End(_startQuery.get());
_curPass = 0;
}
void EffectsProfiler::OnEndPass(ID3D11DeviceContext* d3dDC) noexcept {
if (!IsProfiling()) {
return;
}
d3dDC->End(_passQueries[_curPass++].get());
}
void EffectsProfiler::OnEndEffects(ID3D11DeviceContext* d3dDC) noexcept {
if (!IsProfiling()) {
return;
}
d3dDC->End(_disjointQuery.get());
}
template <typename T>
static T GetQueryData(ID3D11DeviceContext* d3dDC, ID3D11Query* query) noexcept {
T data{};
while (d3dDC->GetData(query, &data, sizeof(data), 0) != S_OK) {
Sleep(0);
}
return data;
}
void EffectsProfiler::QueryTimings(ID3D11DeviceContext* d3dDC) noexcept {
if (!IsProfiling()) {
return;
}
D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjointData =
GetQueryData<D3D11_QUERY_DATA_TIMESTAMP_DISJOINT>(d3dDC, _disjointQuery.get());
if (disjointData.Disjoint) {
return;
}
const float toMS = 1000.0f / disjointData.Frequency;
uint64_t prevTimestamp = GetQueryData<uint64_t>(d3dDC, _startQuery.get());
auto lock = _timingsLock.lock_exclusive();
_timings.resize(_passQueries.size());
for (size_t i = 0; i < _passQueries.size(); ++i) {
uint64_t timestamp = GetQueryData<uint64_t>(d3dDC, _passQueries[i].get());
_timings[i] = (timestamp - prevTimestamp) * toMS;
prevTimestamp = timestamp;
}
}
SmallVector<float> EffectsProfiler::GetTimings() noexcept {
auto lock = _timingsLock.lock_exclusive();
// 没有渲染新帧时 _timings 为空
SmallVector<float> result = std::move(_timings);
_timings.clear();
return result;
}
}

View file

@ -1,45 +0,0 @@
#pragma once
#include "SmallVector.h"
namespace Magpie {
class DeviceResources;
class EffectsProfiler {
public:
EffectsProfiler() = default;
EffectsProfiler(const EffectsProfiler&) = delete;
EffectsProfiler(EffectsProfiler&&) = delete;
void Start(ID3D11Device* d3dDevice, uint32_t passCount) noexcept;
void Stop() noexcept;
bool IsProfiling() const noexcept;
void SetPassCount(ID3D11Device* d3dDevice, uint32_t passCount) noexcept;
void OnBeginEffects(ID3D11DeviceContext* d3dDC) noexcept;
void OnEndPass(ID3D11DeviceContext* d3dDC) noexcept;
void OnEndEffects(ID3D11DeviceContext* d3dDC) noexcept;
void QueryTimings(ID3D11DeviceContext* d3dDC) noexcept;
// 从前端线程调用
SmallVector<float> GetTimings() noexcept;
private:
SmallVector<float> _timings;
wil::srwlock _timingsLock;
winrt::com_ptr<ID3D11Query> _disjointQuery;
winrt::com_ptr<ID3D11Query> _startQuery;
std::vector<winrt::com_ptr<ID3D11Query>> _passQueries;
uint32_t _curPass = 0;
};
}

View file

@ -1,5 +1,6 @@
#pragma once
#include "SmallVector.h"
#include <d3d11_4.h>
#include <ShlObj.h>
#include <winrt/Windows.Graphics.Capture.h>

View file

@ -50,7 +50,6 @@
</FxCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="BackendDescriptorStore.h" />
<ClInclude Include="CatmullRomDrawer.h" />
<ClInclude Include="ColorHelper.h" />
<ClInclude Include="CommandContext.h" />
@ -63,7 +62,6 @@
<ClInclude Include="EffectDrawerBase.h" />
<ClInclude Include="EffectHelper.h" />
<ClInclude Include="EffectsDrawer.h" />
<ClInclude Include="EffectsProfiler.h" />
<ClInclude Include="ExclModeHelper.h" />
<ClInclude Include="FrameProducer.h" />
<ClInclude Include="CursorHelper.h" />
@ -93,7 +91,6 @@
<ClInclude Include="SwapChainPresenter.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="BackendDescriptorStore.cpp" />
<ClCompile Include="CatmullRomDrawer.cpp" />
<ClCompile Include="CommandContext.cpp" />
<ClCompile Include="CursorDrawer.cpp" />
@ -104,7 +101,6 @@
<ClCompile Include="DescriptorHeap.cpp" />
<ClCompile Include="EffectCompiler.cpp" />
<ClCompile Include="EffectsDrawer.cpp" />
<ClCompile Include="EffectsProfiler.cpp" />
<ClCompile Include="ExclModeHelper.cpp" />
<ClCompile Include="FrameProducer.cpp" />
<ClCompile Include="CursorHelper.cpp" />

View file

@ -60,9 +60,6 @@
<ClInclude Include="include\DirectXHelper.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="BackendDescriptorStore.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="CursorDrawer.h">
<Filter>Render</Filter>
</ClInclude>
@ -72,16 +69,10 @@
<ClInclude Include="StepTimer.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="EffectsProfiler.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="DDS.h">
<Filter>Helpers</Filter>
</ClInclude>
<ClInclude Include="SrcTracker.h" />
<ClInclude Include="Renderer2.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="SwapChainPresenter.h">
<Filter>Presenter</Filter>
</ClInclude>
@ -140,6 +131,7 @@
<ClInclude Include="CommandContext.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="Renderer.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="ScalingRuntime.cpp" />
@ -157,9 +149,6 @@
<ClCompile Include="Win32Helper.cpp">
<Filter>Helpers</Filter>
</ClCompile>
<ClCompile Include="BackendDescriptorStore.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="CursorDrawer.cpp">
<Filter>Render</Filter>
</ClCompile>
@ -169,13 +158,7 @@
<ClCompile Include="StepTimer.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="EffectsProfiler.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="SrcTracker.cpp" />
<ClCompile Include="Renderer2.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="SwapChainPresenter.cpp">
<Filter>Presenter</Filter>
</ClCompile>
@ -219,6 +202,7 @@
<ClCompile Include="CommandContext.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="Renderer.cpp" />
</ItemGroup>
<ItemGroup>
<FxCompile Include="shaders\MaskedCursorPS.hlsl">

View file

@ -1,7 +1,8 @@
#include "pch.h"
#include "ShaderEffectParser.h"
#include "EffectInfo.h"
#include "Win32Helper.h"
#include "StrHelper.h"
#include "Logger.h"
#include "ShaderEffectDesc.h"
namespace Magpie {
@ -9,61 +10,233 @@ namespace Magpie {
// 当前 MagpieFX 版本
// static constexpr uint32_t MAGPIE_FX_VERSION = 4;
// static const char* META_INDICATOR = "//!";
// 必须出现在一行的开头才视为指令
static const char* META_INDICATOR = "//!";
class PassInclude : public ID3DInclude {
public:
PassInclude(std::wstring_view localDir) : _localDir(localDir) {}
PassInclude(const PassInclude&) = default;
PassInclude(PassInclude&&) = default;
HRESULT CALLBACK Open(
D3D_INCLUDE_TYPE /*IncludeType*/,
LPCSTR pFileName,
LPCVOID /*pParentData*/,
LPCVOID* ppData,
UINT* pBytes
) noexcept override {
std::wstring relativePath = StrHelper::Concat(_localDir, StrHelper::UTF8ToUTF16(pFileName));
std::string content;
if (!Win32Helper::ReadTextFile(relativePath.c_str(), content)) {
Logger::Get().Win32Error("Win32Helper::ReadTextFile 失败");
return E_FAIL;
}
std::unique_ptr<char[]> data = std::make_unique<char[]>(content.size());
std::memcpy(data.get(), content.data(), content.size());
*ppData = data.release();
*pBytes = (UINT)content.size();
return S_OK;
}
HRESULT CALLBACK Close(LPCVOID pData) noexcept override {
std::unique_ptr<char[]>((char*)pData).reset();
return S_OK;
}
private:
std::wstring _localDir;
struct ParserState {
std::string errorMsg;
uint32_t lineNumber;
bool isNewLine;
};
bool ShaderEffectParser::ParseForInfo(std::string&& name, std::string&& /*source*/, EffectInfo& effectInfo) noexcept {
static void RemoveLeadingSpaces(std::string_view& source, ParserState& state) noexcept {
size_t i = 0;
for (; i < source.size(); ++i) {
if (source[i] != ' ') {
break;
}
}
if (i != 0) {
source.remove_prefix(i);
state.isNewLine = false;
}
}
static bool RemoveLeadingBlanks(std::string_view& source, ParserState& state) noexcept {
size_t i = 0;
for (; i < source.size(); ++i) {
char c = source[i];
if (c == ' ' || c == '\t') {
state.isNewLine = false;
continue;
}
if (c == '\n') {
++state.lineNumber;
state.isNewLine = true;
continue;
}
if (c == '/') {
// source 以换行符结尾,因此可以安全提取非换行符的下一个的字符
char nextChar = source[i + 1];
if (nextChar == '/') {
if (!state.isNewLine || source[i + 2] != '!') {
// 行注释而非指令
i += 2;
while (source[i] != '\n') {
++i;
}
++state.lineNumber;
state.isNewLine = true;
continue;
}
} else if (nextChar == '*') {
// 块注释
i += 2;
while (true) {
c = source[i];
if (c == '*') {
if (source[i + 1] == '/') {
// 块注释结束
++i;
state.isNewLine = false;
break;
}
} else if (c == '\n') {
// 块注释中换行不记为新行
++state.lineNumber;
// 提取换行符的后一个字符需检查文件结尾
if (i + 1 == source.size()) {
state.errorMsg = "块注释未闭合";
return false;
}
}
++i;
}
continue;
}
}
break;
}
source.remove_prefix(i);
return true;
}
template <bool SkipBlanks>
static bool GetNextToken(std::string_view& source, ParserState& state, std::string_view& result) noexcept {
if (SkipBlanks) {
if (!RemoveLeadingBlanks(source, state)) {
return false;
}
} else {
RemoveLeadingSpaces(source, state);
}
if (source.empty()) {
result = source;
return true;
}
char c = source[0];
// 必须以字母或下划线开头
if (!StrHelper::isalpha(c) && c != '_') {
state.errorMsg = fmt::format("Unexpected character \"{}\" in line {}.", c, state.lineNumber);
return false;
}
// 可以包含字母、数字或下划线
size_t i = 1;
for (; i < source.size(); ++i) {
c = source[i];
if (!StrHelper::isalnum(c) && c != '_') {
break;
}
}
result = source.substr(0, i);
source.remove_prefix(i);
state.isNewLine = false;
return true;
}
template <bool SkipBlanks>
static bool CheckNextToken(std::string_view& source, ParserState& state, std::string_view expectedToken) noexcept {
std::string_view token;
if (!GetNextToken<SkipBlanks>(source, state, token)) {
return false;
}
if (token == expectedToken) {
return true;
} else {
state.errorMsg = fmt::format("Unexpected token \"{}\" in line {}.", token, state.lineNumber);
return false;
}
}
static bool CheckMetaIndicator(std::string_view& source, ParserState& state) noexcept {
if (!RemoveLeadingBlanks(source, state)) {
return false;
}
if (state.isNewLine && source.starts_with(META_INDICATOR)) {
source.remove_prefix(StrHelper::StrLen(META_INDICATOR));
state.isNewLine = false;
return true;
} else {
state.errorMsg = fmt::format("Unexpected character \"{}\" in line {}.", source[0], state.lineNumber);
return false;
}
}
static bool CheckLineEnd(std::string_view& source, ParserState& state) noexcept {
RemoveLeadingSpaces(source, state);
if (source.empty() || source[0] == '\n') {
return true;
} else {
state.errorMsg = fmt::format("Unexpected character \"{}\" in line {}.", source[0], state.lineNumber);
return false;
}
}
static bool CheckMagic(std::string_view& source, ParserState& state) noexcept {
if (!CheckMetaIndicator(source, state)) {
return false;
}
if (!CheckNextToken<false>(source, state, "MAGPIE")) {
return false;
}
if (!CheckNextToken<false>(source, state, "EFFECT")) {
return false;
}
return CheckLineEnd(source, state);
}
std::string ShaderEffectParser::ParseForInfo(
std::string&& name,
std::string&& source,
EffectInfo2& effectInfo
) noexcept {
assert(!name.empty() && !source.empty());
effectInfo.name = std::move(name);
return false;
ParserState state = {
.lineNumber = 1,
.isNewLine = true
};
// 确保源代码以换行结尾,这可以有效简化文件结尾检查
if (!source.ends_with('\n')) {
source.push_back('\n');
}
std::string_view sourceView(source);
if (!CheckMagic(sourceView, state)) {
Logger::Get().Error("检查 MagpieFX 头失败");
return std::move(state.errorMsg);
}
return std::move(state.errorMsg);
}
bool ShaderEffectParser::ParseForDesc(
std::string&& name,
std::string&& /*source*/,
std::string_view source,
std::string&& workingFolder,
const ShaderEffectParserOptions& /*options*/,
struct ShaderEffectDesc& effectDesc,
ShaderEffectSource& effectSource
) noexcept {
assert(!name.empty() && !source.empty() && !workingFolder.empty());
effectDesc.name = std::move(name);
effectSource.workingFolder = std::move(workingFolder);
return false;

View file

@ -12,23 +12,20 @@ struct EffectInfoParameter {
float step;
};
enum class EffectInfoFlags {
enum class EffectInfoFlags2 {
None,
SupportFP16 = 1,
// 必须支持一个或 linear sRGB + scRGB
SupportLinearSRGB = 1 << 1,
SupportScRGB = 1 << 2,
SupportSRGB = 1 << 3
SupportAdvancedColor = 1 << 1,
};
DEFINE_ENUM_FLAG_OPERATORS(EffectInfoFlags)
DEFINE_ENUM_FLAG_OPERATORS(EffectInfoFlags2)
struct EffectInfo {
struct EffectInfo2 {
std::string name;
std::string sortName;
std::vector<EffectInfoParameter> params;
// 可使用 INPUT_WIDTH空字符串表示支持自由缩放
std::pair<std::string, std::string> outputSizeExprs;
EffectInfoFlags flags = EffectInfoFlags::SupportLinearSRGB;
EffectInfoFlags2 flags = EffectInfoFlags2::None;
};
}

View file

@ -7,7 +7,7 @@ enum class ShaderEffectParserFlags {
None = 0,
// 只在效果支持 FP16 时影响字节码
EnableFP16 = 1,
// 只在效果同时支持 linear sRGB 和 scRGB 时影响字节码
// 只在效果支持 scRGB 时影响字节码
EnableAdvancedColor = 1 << 1
};
DEFINE_ENUM_FLAG_OPERATORS(ShaderEffectParserFlags)
@ -15,7 +15,7 @@ DEFINE_ENUM_FLAG_OPERATORS(ShaderEffectParserFlags)
struct ShaderEffectParserOptions {
// 只在效果存在参数时影响字节码。为空表示不内联
const phmap::flat_hash_map<std::string, float>* inlineParams = nullptr;
// 只在效果支持 shader model 时影响字节码
// 始终影响字节码
D3D_SHADER_MODEL shaderModel = D3D_SHADER_MODEL_5_1;
ShaderEffectParserFlags flags = ShaderEffectParserFlags::None;
};
@ -29,11 +29,16 @@ struct ShaderEffectSource {
};
struct ShaderEffectParser {
static bool ParseForInfo(std::string&& name, std::string&& source, struct EffectInfo& effectInfo) noexcept;
// 成功时返回空字符串,否则返回错误消息
static std::string ParseForInfo(
std::string&& name,
std::string&& source,
struct EffectInfo2& effectInfo
) noexcept;
static bool ParseForDesc(
std::string&& name,
std::string&& source,
std::string_view source,
std::string&& workingFolder,
const ShaderEffectParserOptions& options,
struct ShaderEffectDesc& effectDesc,

View file

@ -9,7 +9,6 @@
#undef GetCurrentTime
// DirectX 头文件
#include <d3d11_4.h>
#include <dxgi1_6.h>
#include <d3d12.h>
#include <d3dx12.h>

View file

@ -1,6 +1,5 @@
#pragma once
#include "Event.h"
#include <dxgi1_6.h>
namespace Magpie {

View file

@ -15,6 +15,10 @@
#undef GetCurrentTime
#undef GetNextSibling
// DirectX 头文件
#include <dxgi1_6.h>
#include <d3d12.h>
// C++ 运行时
#include <cstdlib>
#include <string>