mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-04 22:31:39 +00:00
Replace the texture-shaped RGB24 output with a linear raw buffer and keep its packing, damage translation, and buffer copies inside the RGB24 effect. Benchmark full-frame native and packed processing and retain the faster path for each source format. Preserve logical damage rectangles for client updates and alternating framebuffer repair. Return compute outputs to COMMON for COPY queue handoff and refresh cached framebuffer sizes when switching packed and native layouts.
578 lines
17 KiB
C++
578 lines
17 KiB
C++
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2026 The Looking Glass Authors
|
|
* https://looking-glass.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "CRGB24Effect.h"
|
|
|
|
#include "CDebug.h"
|
|
#include "../CSettings.h"
|
|
#include "common/LGMPConfig.h"
|
|
|
|
#include <algorithm>
|
|
#include <climits>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
|
|
using namespace PostProcessUtil;
|
|
|
|
static_assert(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT == 256,
|
|
"RGB24 shader row alignment must match D3D12");
|
|
|
|
struct CRGB24Effect::State
|
|
{
|
|
enum class Phase
|
|
{
|
|
DISABLED,
|
|
NATIVE_WARMUP,
|
|
NATIVE_SAMPLE,
|
|
PACKED_WARMUP,
|
|
PACKED_SAMPLE,
|
|
LOCKED_NATIVE,
|
|
LOCKED_PACKED,
|
|
};
|
|
|
|
struct FormatKey
|
|
{
|
|
D3D12_RESOURCE_DIMENSION resourceDimension = D3D12_RESOURCE_DIMENSION_UNKNOWN;
|
|
UINT64 resourceWidth = 0;
|
|
UINT resourceHeight = 0;
|
|
DXGI_FORMAT resourceFormat = DXGI_FORMAT_UNKNOWN;
|
|
unsigned width = 0;
|
|
unsigned height = 0;
|
|
FrameType format = FRAME_TYPE_INVALID;
|
|
bool hdr = false;
|
|
bool hdrPQ = false;
|
|
std::shared_ptr<const D12ColorTransform> colorTransform;
|
|
};
|
|
|
|
static const unsigned WarmupCount = LGMP_Q_FRAME_LEN;
|
|
static const unsigned SampleCount = 64;
|
|
static const unsigned TrimCount = SampleCount / 8;
|
|
|
|
SRWLOCK lock = SRWLOCK_INIT;
|
|
Phase phase = Phase::DISABLED;
|
|
FormatKey format = {};
|
|
bool formatValid = false;
|
|
uint64_t generation = 0;
|
|
unsigned warmups = 0;
|
|
unsigned sampleCount = 0;
|
|
uint64_t nativeMean = 0;
|
|
uint64_t samples[SampleCount] = {};
|
|
|
|
static bool IsEligible(const D12FrameFormat& format)
|
|
{
|
|
if (format.hdr ||
|
|
format.desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D ||
|
|
format.desc.Format != DXGI_FORMAT_B8G8R8A8_UNORM)
|
|
return false;
|
|
|
|
if (!format.colorTransform ||
|
|
(!format.colorTransform->matrixEnabled &&
|
|
!format.colorTransform->lutEnabled))
|
|
return true;
|
|
|
|
return IsIdentityColorTransform(*format.colorTransform);
|
|
}
|
|
|
|
bool WantsPackedLocked() const
|
|
{
|
|
return phase == Phase::PACKED_WARMUP ||
|
|
phase == Phase::PACKED_SAMPLE ||
|
|
phase == Phase::LOCKED_PACKED;
|
|
}
|
|
|
|
bool IsBenchmarkingLocked() const
|
|
{
|
|
return phase == Phase::NATIVE_WARMUP ||
|
|
phase == Phase::NATIVE_SAMPLE ||
|
|
phase == Phase::PACKED_WARMUP ||
|
|
phase == Phase::PACKED_SAMPLE;
|
|
}
|
|
|
|
void ResetStageLocked()
|
|
{
|
|
warmups = 0;
|
|
sampleCount = 0;
|
|
std::memset(samples, 0, sizeof(samples));
|
|
}
|
|
|
|
uint64_t TrimmedMeanLocked()
|
|
{
|
|
std::sort(samples, samples + SampleCount);
|
|
|
|
uint64_t total = 0;
|
|
for (unsigned i = TrimCount; i < SampleCount - TrimCount; ++i)
|
|
total += samples[i];
|
|
|
|
return total / (SampleCount - TrimCount * 2);
|
|
}
|
|
|
|
void Update(const D12FrameFormat& next)
|
|
{
|
|
AcquireSRWLockExclusive(&lock);
|
|
|
|
const bool formatChanged = !formatValid ||
|
|
format.resourceDimension != next.desc.Dimension ||
|
|
format.resourceWidth != next.desc.Width ||
|
|
format.resourceHeight != next.desc.Height ||
|
|
format.resourceFormat != next.desc.Format ||
|
|
format.width != next.width ||
|
|
format.height != next.height ||
|
|
format.format != next.format ||
|
|
format.hdr != next.hdr ||
|
|
format.hdrPQ != next.hdrPQ ||
|
|
format.colorTransform != next.colorTransform;
|
|
|
|
if (formatChanged)
|
|
{
|
|
format.resourceDimension = next.desc.Dimension;
|
|
format.resourceWidth = next.desc.Width;
|
|
format.resourceHeight = next.desc.Height;
|
|
format.resourceFormat = next.desc.Format;
|
|
format.width = next.width;
|
|
format.height = next.height;
|
|
format.format = next.format;
|
|
format.hdr = next.hdr;
|
|
format.hdrPQ = next.hdrPQ;
|
|
format.colorTransform = next.colorTransform;
|
|
formatValid = true;
|
|
|
|
phase = IsEligible(next) ?
|
|
Phase::NATIVE_WARMUP : Phase::DISABLED;
|
|
nativeMean = 0;
|
|
++generation;
|
|
ResetStageLocked();
|
|
}
|
|
|
|
switch (phase)
|
|
{
|
|
case Phase::NATIVE_WARMUP:
|
|
if (warmups >= WarmupCount)
|
|
{
|
|
phase = Phase::NATIVE_SAMPLE;
|
|
++generation;
|
|
ResetStageLocked();
|
|
}
|
|
break;
|
|
|
|
case Phase::NATIVE_SAMPLE:
|
|
if (sampleCount >= SampleCount)
|
|
{
|
|
nativeMean = TrimmedMeanLocked();
|
|
phase = Phase::PACKED_WARMUP;
|
|
++generation;
|
|
ResetStageLocked();
|
|
}
|
|
break;
|
|
|
|
case Phase::PACKED_WARMUP:
|
|
if (warmups >= WarmupCount)
|
|
{
|
|
phase = Phase::PACKED_SAMPLE;
|
|
++generation;
|
|
ResetStageLocked();
|
|
}
|
|
break;
|
|
|
|
case Phase::PACKED_SAMPLE:
|
|
if (sampleCount >= SampleCount)
|
|
{
|
|
const uint64_t packedMean = TrimmedMeanLocked();
|
|
const uint64_t relativeThreshold = nativeMean / 20;
|
|
const uint64_t threshold = relativeThreshold > 50000ULL ?
|
|
relativeThreshold : 50000ULL;
|
|
// Prefer the bandwidth saving unless native is meaningfully faster.
|
|
const bool usePacked = packedMean <= nativeMean ||
|
|
packedMean - nativeMean <= threshold;
|
|
|
|
DEBUG_INFO(
|
|
"RGB24 benchmark: native=%llu us, packed=%llu us, selected=%s",
|
|
(unsigned long long)(nativeMean / 1000),
|
|
(unsigned long long)(packedMean / 1000),
|
|
usePacked ? "packed" : "native");
|
|
|
|
phase = usePacked ?
|
|
Phase::LOCKED_PACKED : Phase::LOCKED_NATIVE;
|
|
++generation;
|
|
ResetStageLocked();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ReleaseSRWLockExclusive(&lock);
|
|
}
|
|
|
|
bool WantsPacked()
|
|
{
|
|
AcquireSRWLockShared(&lock);
|
|
const bool result = WantsPackedLocked();
|
|
ReleaseSRWLockShared(&lock);
|
|
return result;
|
|
}
|
|
|
|
bool IsBenchmarking()
|
|
{
|
|
AcquireSRWLockShared(&lock);
|
|
const bool result = IsBenchmarkingLocked();
|
|
ReleaseSRWLockShared(&lock);
|
|
return result;
|
|
}
|
|
|
|
uint64_t GetTimingToken(bool packed)
|
|
{
|
|
AcquireSRWLockShared(&lock);
|
|
|
|
const uint64_t result = IsBenchmarkingLocked() &&
|
|
packed == WantsPackedLocked() ? generation : 0;
|
|
|
|
ReleaseSRWLockShared(&lock);
|
|
return result;
|
|
}
|
|
|
|
void Reject()
|
|
{
|
|
AcquireSRWLockExclusive(&lock);
|
|
if (WantsPackedLocked())
|
|
{
|
|
phase = Phase::LOCKED_NATIVE;
|
|
++generation;
|
|
ResetStageLocked();
|
|
}
|
|
ReleaseSRWLockExclusive(&lock);
|
|
}
|
|
|
|
void RecordTiming(uint64_t token, bool fullCopy, uint64_t totalTime)
|
|
{
|
|
AcquireSRWLockExclusive(&lock);
|
|
|
|
if (token == generation && fullCopy)
|
|
switch (phase)
|
|
{
|
|
case Phase::NATIVE_WARMUP:
|
|
case Phase::PACKED_WARMUP:
|
|
++warmups;
|
|
break;
|
|
|
|
case Phase::NATIVE_SAMPLE:
|
|
case Phase::PACKED_SAMPLE:
|
|
if (sampleCount < SampleCount)
|
|
samples[sampleCount++] = totalTime;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ReleaseSRWLockExclusive(&lock);
|
|
}
|
|
};
|
|
|
|
bool CRGB24Effect::Init(const ComPtr<ID3D12Device3>& device)
|
|
{
|
|
if (!g_settings.ReadBoolValue(L"AllowRGB24", true))
|
|
return false;
|
|
|
|
D3D12_DESCRIPTOR_RANGE ranges[2] = {};
|
|
ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
|
|
ranges[0].NumDescriptors = 1;
|
|
ranges[0].BaseShaderRegister = 0;
|
|
ranges[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
|
|
ranges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
|
|
ranges[1].NumDescriptors = 1;
|
|
ranges[1].BaseShaderRegister = 0;
|
|
ranges[1].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
|
|
|
|
const char * shader =
|
|
"Texture2D<float4> src : register(t0);\n"
|
|
"RWByteAddressBuffer dst : register(u0);\n"
|
|
"[numthreads(" POST_PROCESS_THREADS_STR ", " POST_PROCESS_THREADS_STR ", 1)]\n"
|
|
"void main(uint3 dt : SV_DispatchThreadID)\n"
|
|
"{\n"
|
|
" uint width, height;\n"
|
|
" src.GetDimensions(width, height);\n"
|
|
" uint rowBytes = width * 3;\n"
|
|
" uint dataWidth = ((rowBytes + 255) & ~255u) / 4;\n"
|
|
" if (dt.x >= dataWidth || dt.y >= height)\n"
|
|
" return;\n"
|
|
" uint rowOffset = dt.x * 4;\n"
|
|
" if (rowOffset >= rowBytes)\n"
|
|
" {\n"
|
|
" dst.Store((dt.y * dataWidth + dt.x) * 4, 0);\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" uint firstX = (dt.x * 4) / 3;\n"
|
|
" uint secondX = firstX + 1;\n"
|
|
" float4 color0 = src[uint2(firstX, dt.y)];\n"
|
|
" float4 color3 = secondX < width ?\n"
|
|
" src[uint2(secondX, dt.y)] : 0.0f;\n"
|
|
" uint xmod3 = dt.x % 3;\n"
|
|
" float4 color1 = xmod3 <= 1 ? color0 : color3;\n"
|
|
" float4 color2 = xmod3 == 0 ? color0 : color3;\n"
|
|
" float4 packed = float4(\n"
|
|
" color0.bgr[xmod3], color1.grb[xmod3],\n"
|
|
" color2.rbg[xmod3], color3.bgr[xmod3]);\n"
|
|
" uint4 bytes = (uint4)(saturate(packed) * 255.0f + 0.5f);\n"
|
|
" uint value = bytes.x | (bytes.y << 8) |\n"
|
|
" (bytes.z << 16) | (bytes.w << 24);\n"
|
|
" dst.Store((dt.y * dataWidth + dt.x) * 4, value);\n"
|
|
"}\n";
|
|
|
|
if (!InitCompute(device, ranges, ARRAYSIZE(ranges), nullptr, 0, shader))
|
|
return false;
|
|
|
|
m_state = std::make_shared<State>();
|
|
return true;
|
|
}
|
|
|
|
void CRGB24Effect::ShareState(const CPostProcessEffect& other)
|
|
{
|
|
m_state = static_cast<const CRGB24Effect&>(other).m_state;
|
|
}
|
|
|
|
void CRGB24Effect::Update(const D12FrameFormat& format)
|
|
{
|
|
m_state->Update(format);
|
|
}
|
|
|
|
bool CRGB24Effect::NeedsReconfigure() const
|
|
{
|
|
return m_state->WantsPacked() != Enabled;
|
|
}
|
|
|
|
bool CRGB24Effect::RequiresFullDamage() const
|
|
{
|
|
return m_state->IsBenchmarking();
|
|
}
|
|
|
|
uint64_t CRGB24Effect::GetTimingToken() const
|
|
{
|
|
return m_state->GetTimingToken(Enabled);
|
|
}
|
|
|
|
void CRGB24Effect::RecordTiming(
|
|
uint64_t token, bool fullCopy, uint64_t totalTime)
|
|
{
|
|
m_state->RecordTiming(token, fullCopy, totalTime);
|
|
}
|
|
|
|
bool CRGB24Effect::GetCopyLayout(
|
|
unsigned * pitch, unsigned * dataHeight) const
|
|
{
|
|
*pitch = m_pitch;
|
|
*dataHeight = m_height;
|
|
return true;
|
|
}
|
|
|
|
static void GetCopySpan(const RECT& rect, unsigned width,
|
|
unsigned pitch, UINT64 * left, UINT64 * right)
|
|
{
|
|
// Damage stays in logical pixels until the packed copy is recorded.
|
|
*left = ((UINT64)rect.left * 3) & ~3ULL;
|
|
if (rect.left == 0 && rect.right == (LONG)width)
|
|
*right = pitch;
|
|
else
|
|
*right = ((UINT64)rect.right * 3 + 3) & ~3ULL;
|
|
}
|
|
|
|
bool CRGB24Effect::ShouldCopyFully(
|
|
const RECT dirtyRects[], unsigned nbDirtyRects) const
|
|
{
|
|
static const unsigned commandLimit = 256;
|
|
|
|
const uint64_t frameSize = (uint64_t)m_pitch * m_height;
|
|
uint64_t copiedBytes = 0;
|
|
unsigned commands = 0;
|
|
for (const RECT * rect = dirtyRects;
|
|
rect < dirtyRects + nbDirtyRects; ++rect)
|
|
{
|
|
UINT64 left;
|
|
UINT64 right;
|
|
GetCopySpan(*rect, m_width, m_pitch, &left, &right);
|
|
|
|
const unsigned rows = (unsigned)(rect->bottom - rect->top);
|
|
if (left == 0 && right == m_pitch)
|
|
{
|
|
++commands;
|
|
copiedBytes += (uint64_t)rows * m_pitch;
|
|
}
|
|
else
|
|
{
|
|
commands += rows;
|
|
copiedBytes += (right - left) * rows;
|
|
}
|
|
|
|
if (commands > commandLimit || copiedBytes >= frameSize)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CRGB24Effect::CopyFrame(
|
|
const ComPtr<ID3D12GraphicsCommandList>& commandList,
|
|
ID3D12Resource * dst, ID3D12Resource * src,
|
|
const RECT dirtyRects[], unsigned nbDirtyRects, bool fullCopy) const
|
|
{
|
|
if (fullCopy)
|
|
{
|
|
commandList->CopyBufferRegion(
|
|
dst, 0, src, 0, (UINT64)m_pitch * m_height);
|
|
return;
|
|
}
|
|
|
|
for (const RECT * rect = dirtyRects;
|
|
rect < dirtyRects + nbDirtyRects; ++rect)
|
|
{
|
|
UINT64 left;
|
|
UINT64 right;
|
|
GetCopySpan(*rect, m_width, m_pitch, &left, &right);
|
|
|
|
if (left == 0 && right == m_pitch)
|
|
{
|
|
const UINT64 offset = (UINT64)rect->top * m_pitch;
|
|
const UINT64 size =
|
|
(UINT64)(rect->bottom - rect->top) * m_pitch;
|
|
commandList->CopyBufferRegion(dst, offset, src, offset, size);
|
|
continue;
|
|
}
|
|
|
|
for (LONG y = rect->top; y < rect->bottom; ++y)
|
|
{
|
|
const UINT64 offset = (UINT64)y * m_pitch + left;
|
|
commandList->CopyBufferRegion(
|
|
dst, offset, src, offset, right - left);
|
|
}
|
|
}
|
|
}
|
|
|
|
PostProcessStatus CRGB24Effect::SetFormat(const ComPtr<ID3D12Device3>& device,
|
|
const D12FrameFormat& src, D12FrameFormat& dst)
|
|
{
|
|
if (!m_state->WantsPacked())
|
|
{
|
|
m_dst.Reset();
|
|
return PostProcessStatus::BYPASS_EFFECT;
|
|
}
|
|
|
|
if (src.desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D ||
|
|
src.desc.Format != DXGI_FORMAT_B8G8R8A8_UNORM ||
|
|
src.hdr)
|
|
{
|
|
DEBUG_WARN("RGB24 packing is unavailable for the current format");
|
|
m_state->Reject();
|
|
m_dst.Reset();
|
|
return PostProcessStatus::BYPASS_EFFECT;
|
|
}
|
|
|
|
if (src.desc.Width >
|
|
(UINT64_MAX - (D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1)) / 3)
|
|
{
|
|
m_state->Reject();
|
|
m_dst.Reset();
|
|
return PostProcessStatus::BYPASS_EFFECT;
|
|
}
|
|
|
|
const UINT64 packedPitch = AlignTo<UINT64>(
|
|
src.desc.Width * 3, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
|
|
if (!src.desc.Height || packedPitch > LONG_MAX ||
|
|
packedPitch > UINT64_MAX / src.desc.Height)
|
|
{
|
|
m_state->Reject();
|
|
m_dst.Reset();
|
|
return PostProcessStatus::BYPASS_EFFECT;
|
|
}
|
|
|
|
const UINT64 bufferSize = packedPitch * src.desc.Height;
|
|
const UINT64 maxUAVSize =
|
|
(1ULL << D3D12_REQ_BUFFER_RESOURCE_TEXEL_COUNT_2_TO_EXP) *
|
|
sizeof(uint32_t);
|
|
if (bufferSize > UINT32_MAX || bufferSize > maxUAVSize)
|
|
{
|
|
m_state->Reject();
|
|
m_dst.Reset();
|
|
return PostProcessStatus::BYPASS_EFFECT;
|
|
}
|
|
|
|
if (!CreateDefaultBuffer(device, bufferSize, m_dst))
|
|
{
|
|
m_state->Reject();
|
|
m_dst.Reset();
|
|
return PostProcessStatus::BYPASS_EFFECT;
|
|
}
|
|
|
|
const unsigned dataWidth = (unsigned)(packedPitch / 4);
|
|
m_threadsX = (dataWidth + (Threads - 1)) / Threads;
|
|
m_threadsY = (src.desc.Height + (Threads - 1)) / Threads;
|
|
m_width = (unsigned)src.desc.Width;
|
|
m_height = src.desc.Height;
|
|
m_pitch = (unsigned)packedPitch;
|
|
|
|
dst.desc = m_dst->GetDesc();
|
|
dst.dataWidth = dataWidth;
|
|
dst.dataHeight = src.desc.Height;
|
|
dst.pitch = m_pitch;
|
|
dst.format = FRAME_TYPE_BGR_32;
|
|
return PostProcessStatus::SUCCESS;
|
|
}
|
|
|
|
ComPtr<ID3D12Resource> CRGB24Effect::Run(const ComPtr<ID3D12Device3>& device,
|
|
const ComPtr<ID3D12GraphicsCommandList>& commandList,
|
|
const ComPtr<ID3D12Resource>& src, RECT dirtyRects[],
|
|
unsigned * nbDirtyRects)
|
|
{
|
|
UNREFERENCED_PARAMETER(dirtyRects);
|
|
UNREFERENCED_PARAMETER(nbDirtyRects);
|
|
|
|
TransitionDst(commandList, D3D12_RESOURCE_STATE_COMMON,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
|
|
D3D12_CPU_DESCRIPTOR_HANDLE handle =
|
|
m_descHeap->GetCPUDescriptorHandleForHeapStart();
|
|
const UINT inc = device->GetDescriptorHandleIncrementSize(
|
|
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
|
|
srvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
|
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
srvDesc.Texture2D.MipLevels = 1;
|
|
device->CreateShaderResourceView(src.Get(), &srvDesc, handle);
|
|
handle.ptr += inc;
|
|
|
|
D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
|
|
uavDesc.Format = DXGI_FORMAT_R32_TYPELESS;
|
|
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
|
|
uavDesc.Buffer.NumElements = (UINT)(m_dst->GetDesc().Width / 4);
|
|
uavDesc.Buffer.StructureByteStride = 0;
|
|
uavDesc.Buffer.CounterOffsetInBytes = 0;
|
|
uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW;
|
|
device->CreateUnorderedAccessView(m_dst.Get(), nullptr, &uavDesc, handle);
|
|
|
|
Bind(commandList);
|
|
commandList->Dispatch(m_threadsX, m_threadsY, 1);
|
|
|
|
TransitionDst(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
|
D3D12_RESOURCE_STATE_COMMON);
|
|
|
|
return m_dst;
|
|
}
|