[GPU] Split SharedMemory into common and D3D12
This commit is contained in:
459
src/xenia/gpu/d3d12/d3d12_shared_memory.cc
Normal file
459
src/xenia/gpu/d3d12/d3d12_shared_memory.cc
Normal file
@@ -0,0 +1,459 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/gpu/d3d12/d3d12_shared_memory.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/profiling.h"
|
||||
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
||||
#include "xenia/ui/d3d12/d3d12_util.h"
|
||||
|
||||
DEFINE_bool(d3d12_tiled_shared_memory, true,
|
||||
"Enable tiled resources for shared memory emulation. Disabling "
|
||||
"them greatly increases video memory usage - a 512 MB buffer is "
|
||||
"created - but allows graphics debuggers that don't support tiled "
|
||||
"resources to work.",
|
||||
"D3D12");
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace d3d12 {
|
||||
|
||||
D3D12SharedMemory::D3D12SharedMemory(D3D12CommandProcessor& command_processor,
|
||||
Memory& memory, TraceWriter& trace_writer)
|
||||
: SharedMemory(memory),
|
||||
command_processor_(command_processor),
|
||||
trace_writer_(trace_writer) {}
|
||||
|
||||
D3D12SharedMemory::~D3D12SharedMemory() { Shutdown(true); }
|
||||
|
||||
bool D3D12SharedMemory::Initialize() {
|
||||
InitializeCommon();
|
||||
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
|
||||
D3D12_RESOURCE_DESC buffer_desc;
|
||||
ui::d3d12::util::FillBufferResourceDesc(
|
||||
buffer_desc, kBufferSize, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
||||
buffer_state_ = D3D12_RESOURCE_STATE_COPY_DEST;
|
||||
if (AreTiledResourcesUsed()) {
|
||||
if (FAILED(device->CreateReservedResource(
|
||||
&buffer_desc, buffer_state_, nullptr, IID_PPV_ARGS(&buffer_)))) {
|
||||
XELOGE("Shared memory: Failed to create the 512 MB tiled buffer");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
XELOGGPU(
|
||||
"Direct3D 12 tiled resources are not used for shared memory "
|
||||
"emulation - video memory usage may increase significantly "
|
||||
"because a full 512 MB buffer will be created!");
|
||||
if (provider.GetGraphicsAnalysis() != nullptr) {
|
||||
// As of October 8th, 2018, PIX doesn't support tiled buffers.
|
||||
// FIXME(Triang3l): Re-enable tiled resources with PIX once fixed.
|
||||
XELOGGPU(
|
||||
"This is caused by PIX being attached, which doesn't support tiled "
|
||||
"resources yet.");
|
||||
}
|
||||
if (FAILED(device->CreateCommittedResource(
|
||||
&ui::d3d12::util::kHeapPropertiesDefault,
|
||||
provider.GetHeapFlagCreateNotZeroed(), &buffer_desc, buffer_state_,
|
||||
nullptr, IID_PPV_ARGS(&buffer_)))) {
|
||||
XELOGE("Shared memory: Failed to create the 512 MB buffer");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
buffer_gpu_address_ = buffer_->GetGPUVirtualAddress();
|
||||
buffer_uav_writes_commit_needed_ = false;
|
||||
|
||||
D3D12_DESCRIPTOR_HEAP_DESC buffer_descriptor_heap_desc;
|
||||
buffer_descriptor_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
||||
buffer_descriptor_heap_desc.NumDescriptors =
|
||||
uint32_t(BufferDescriptorIndex::kCount);
|
||||
buffer_descriptor_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
|
||||
buffer_descriptor_heap_desc.NodeMask = 0;
|
||||
if (FAILED(device->CreateDescriptorHeap(
|
||||
&buffer_descriptor_heap_desc,
|
||||
IID_PPV_ARGS(&buffer_descriptor_heap_)))) {
|
||||
XELOGE(
|
||||
"Failed to create the descriptor heap for shared memory buffer views");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
buffer_descriptor_heap_start_ =
|
||||
buffer_descriptor_heap_->GetCPUDescriptorHandleForHeapStart();
|
||||
ui::d3d12::util::CreateBufferRawSRV(
|
||||
device,
|
||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||
uint32_t(BufferDescriptorIndex::kRawSRV)),
|
||||
buffer_, kBufferSize);
|
||||
ui::d3d12::util::CreateBufferTypedSRV(
|
||||
device,
|
||||
provider.OffsetViewDescriptor(
|
||||
buffer_descriptor_heap_start_,
|
||||
uint32_t(BufferDescriptorIndex::kR32UintSRV)),
|
||||
buffer_, DXGI_FORMAT_R32_UINT, kBufferSize >> 2);
|
||||
ui::d3d12::util::CreateBufferTypedSRV(
|
||||
device,
|
||||
provider.OffsetViewDescriptor(
|
||||
buffer_descriptor_heap_start_,
|
||||
uint32_t(BufferDescriptorIndex::kR32G32UintSRV)),
|
||||
buffer_, DXGI_FORMAT_R32G32_UINT, kBufferSize >> 3);
|
||||
ui::d3d12::util::CreateBufferTypedSRV(
|
||||
device,
|
||||
provider.OffsetViewDescriptor(
|
||||
buffer_descriptor_heap_start_,
|
||||
uint32_t(BufferDescriptorIndex::kR32G32B32A32UintSRV)),
|
||||
buffer_, DXGI_FORMAT_R32G32B32A32_UINT, kBufferSize >> 4);
|
||||
ui::d3d12::util::CreateBufferRawUAV(
|
||||
device,
|
||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||
uint32_t(BufferDescriptorIndex::kRawUAV)),
|
||||
buffer_, kBufferSize);
|
||||
ui::d3d12::util::CreateBufferTypedUAV(
|
||||
device,
|
||||
provider.OffsetViewDescriptor(
|
||||
buffer_descriptor_heap_start_,
|
||||
uint32_t(BufferDescriptorIndex::kR32UintUAV)),
|
||||
buffer_, DXGI_FORMAT_R32_UINT, kBufferSize >> 2);
|
||||
ui::d3d12::util::CreateBufferTypedUAV(
|
||||
device,
|
||||
provider.OffsetViewDescriptor(
|
||||
buffer_descriptor_heap_start_,
|
||||
uint32_t(BufferDescriptorIndex::kR32G32UintUAV)),
|
||||
buffer_, DXGI_FORMAT_R32G32_UINT, kBufferSize >> 3);
|
||||
ui::d3d12::util::CreateBufferTypedUAV(
|
||||
device,
|
||||
provider.OffsetViewDescriptor(
|
||||
buffer_descriptor_heap_start_,
|
||||
uint32_t(BufferDescriptorIndex::kR32G32B32A32UintUAV)),
|
||||
buffer_, DXGI_FORMAT_R32G32B32A32_UINT, kBufferSize >> 4);
|
||||
|
||||
upload_buffer_pool_ = std::make_unique<ui::d3d12::D3D12UploadBufferPool>(
|
||||
provider, xe::align(ui::d3d12::D3D12UploadBufferPool::kDefaultPageSize,
|
||||
size_t(1) << page_size_log2()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12SharedMemory::Shutdown(bool from_destructor) {
|
||||
ResetTraceDownload();
|
||||
|
||||
upload_buffer_pool_.reset();
|
||||
|
||||
ui::d3d12::util::ReleaseAndNull(buffer_descriptor_heap_);
|
||||
|
||||
// First free the buffer to detach it from the heaps.
|
||||
ui::d3d12::util::ReleaseAndNull(buffer_);
|
||||
|
||||
if (AreTiledResourcesUsed()) {
|
||||
for (uint32_t i = 0; i < xe::countof(heaps_); ++i) {
|
||||
ui::d3d12::util::ReleaseAndNull(heaps_[i]);
|
||||
}
|
||||
heap_count_ = 0;
|
||||
COUNT_profile_set("gpu/shared_memory/used_mb", 0);
|
||||
}
|
||||
|
||||
// If calling from the destructor, the SharedMemory destructor will call
|
||||
// ShutdownCommon.
|
||||
if (!from_destructor) {
|
||||
ShutdownCommon();
|
||||
}
|
||||
}
|
||||
|
||||
void D3D12SharedMemory::ClearCache() {
|
||||
SharedMemory::ClearCache();
|
||||
|
||||
upload_buffer_pool_->ClearCache();
|
||||
|
||||
// TODO(Triang3l): Unmap and destroy heaps.
|
||||
}
|
||||
|
||||
void D3D12SharedMemory::CompletedSubmissionUpdated() {
|
||||
upload_buffer_pool_->Reclaim(command_processor_.GetCompletedSubmission());
|
||||
}
|
||||
|
||||
bool D3D12SharedMemory::AreTiledResourcesUsed() const {
|
||||
if (!cvars::d3d12_tiled_shared_memory) {
|
||||
return false;
|
||||
}
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
// As of October 8th, 2018, PIX doesn't support tiled buffers.
|
||||
// FIXME(Triang3l): Re-enable tiled resources with PIX once fixed.
|
||||
return provider.GetTiledResourcesTier() !=
|
||||
D3D12_TILED_RESOURCES_TIER_NOT_SUPPORTED &&
|
||||
provider.GetGraphicsAnalysis() == nullptr;
|
||||
}
|
||||
|
||||
void D3D12SharedMemory::CommitUAVWritesAndTransitionBuffer(
|
||||
D3D12_RESOURCE_STATES new_state) {
|
||||
if (buffer_state_ == new_state) {
|
||||
if (new_state == D3D12_RESOURCE_STATE_UNORDERED_ACCESS &&
|
||||
buffer_uav_writes_commit_needed_) {
|
||||
command_processor_.PushUAVBarrier(buffer_);
|
||||
buffer_uav_writes_commit_needed_ = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
command_processor_.PushTransitionBarrier(buffer_, buffer_state_, new_state);
|
||||
buffer_state_ = new_state;
|
||||
// "UAV -> anything" transition commits the writes implicitly.
|
||||
buffer_uav_writes_commit_needed_ = false;
|
||||
}
|
||||
|
||||
void D3D12SharedMemory::WriteRawSRVDescriptor(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
device->CopyDescriptorsSimple(
|
||||
1, handle,
|
||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||
uint32_t(BufferDescriptorIndex::kRawSRV)),
|
||||
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
||||
}
|
||||
|
||||
void D3D12SharedMemory::WriteRawUAVDescriptor(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
device->CopyDescriptorsSimple(
|
||||
1, handle,
|
||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||
uint32_t(BufferDescriptorIndex::kRawUAV)),
|
||||
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
||||
}
|
||||
|
||||
void D3D12SharedMemory::WriteUintPow2SRVDescriptor(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE handle, uint32_t element_size_bytes_pow2) {
|
||||
BufferDescriptorIndex descriptor_index;
|
||||
switch (element_size_bytes_pow2) {
|
||||
case 2:
|
||||
descriptor_index = BufferDescriptorIndex::kR32UintSRV;
|
||||
break;
|
||||
case 3:
|
||||
descriptor_index = BufferDescriptorIndex::kR32G32UintSRV;
|
||||
break;
|
||||
case 4:
|
||||
descriptor_index = BufferDescriptorIndex::kR32G32B32A32UintSRV;
|
||||
break;
|
||||
default:
|
||||
assert_unhandled_case(element_size_bytes_pow2);
|
||||
return;
|
||||
}
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
device->CopyDescriptorsSimple(
|
||||
1, handle,
|
||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||
uint32_t(descriptor_index)),
|
||||
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
||||
}
|
||||
|
||||
void D3D12SharedMemory::WriteUintPow2UAVDescriptor(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE handle, uint32_t element_size_bytes_pow2) {
|
||||
BufferDescriptorIndex descriptor_index;
|
||||
switch (element_size_bytes_pow2) {
|
||||
case 2:
|
||||
descriptor_index = BufferDescriptorIndex::kR32UintUAV;
|
||||
break;
|
||||
case 3:
|
||||
descriptor_index = BufferDescriptorIndex::kR32G32UintUAV;
|
||||
break;
|
||||
case 4:
|
||||
descriptor_index = BufferDescriptorIndex::kR32G32B32A32UintUAV;
|
||||
break;
|
||||
default:
|
||||
assert_unhandled_case(element_size_bytes_pow2);
|
||||
return;
|
||||
}
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
device->CopyDescriptorsSimple(
|
||||
1, handle,
|
||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||
uint32_t(descriptor_index)),
|
||||
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
||||
}
|
||||
|
||||
bool D3D12SharedMemory::InitializeTraceSubmitDownloads() {
|
||||
ResetTraceDownload();
|
||||
PrepareForTraceDownload();
|
||||
uint32_t download_page_count = trace_download_page_count();
|
||||
// Request downloading of GPU-written memory.
|
||||
if (!download_page_count) {
|
||||
return false;
|
||||
}
|
||||
D3D12_RESOURCE_DESC download_buffer_desc;
|
||||
ui::d3d12::util::FillBufferResourceDesc(
|
||||
download_buffer_desc, download_page_count << page_size_log2(),
|
||||
D3D12_RESOURCE_FLAG_NONE);
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
if (FAILED(device->CreateCommittedResource(
|
||||
&ui::d3d12::util::kHeapPropertiesReadback,
|
||||
provider.GetHeapFlagCreateNotZeroed(), &download_buffer_desc,
|
||||
D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
|
||||
IID_PPV_ARGS(&trace_download_buffer_)))) {
|
||||
XELOGE(
|
||||
"Shared memory: Failed to create a {} KB GPU-written memory download "
|
||||
"buffer for frame tracing",
|
||||
download_page_count << page_size_log2() >> 10);
|
||||
ResetTraceDownload();
|
||||
return false;
|
||||
}
|
||||
auto& command_list = command_processor_.GetDeferredCommandList();
|
||||
UseAsCopySource();
|
||||
command_processor_.SubmitBarriers();
|
||||
uint32_t download_buffer_offset = 0;
|
||||
for (auto& download_range : trace_download_ranges()) {
|
||||
if (!EnsureHostGpuMemoryAllocated(download_range.first,
|
||||
download_range.second)) {
|
||||
download_range.second = 0;
|
||||
continue;
|
||||
}
|
||||
command_list.D3DCopyBufferRegion(
|
||||
trace_download_buffer_, download_buffer_offset, buffer_,
|
||||
download_range.first, download_range.second);
|
||||
download_buffer_offset += download_range.second;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12SharedMemory::InitializeTraceCompleteDownloads() {
|
||||
if (!trace_download_buffer_) {
|
||||
return;
|
||||
}
|
||||
void* download_mapping;
|
||||
if (SUCCEEDED(trace_download_buffer_->Map(0, nullptr, &download_mapping))) {
|
||||
uint32_t download_buffer_offset = 0;
|
||||
for (auto download_range : trace_download_ranges()) {
|
||||
trace_writer_.WriteMemoryRead(
|
||||
download_range.first, download_range.second,
|
||||
reinterpret_cast<const uint8_t*>(download_mapping) +
|
||||
download_buffer_offset);
|
||||
}
|
||||
D3D12_RANGE download_write_range = {};
|
||||
trace_download_buffer_->Unmap(0, &download_write_range);
|
||||
} else {
|
||||
XELOGE(
|
||||
"Failed to map the GPU-written memory download buffer for frame "
|
||||
"tracing");
|
||||
}
|
||||
ResetTraceDownload();
|
||||
}
|
||||
|
||||
void D3D12SharedMemory::ResetTraceDownload() {
|
||||
ui::d3d12::util::ReleaseAndNull(trace_download_buffer_);
|
||||
ReleaseTraceDownloadRanges();
|
||||
}
|
||||
|
||||
bool D3D12SharedMemory::EnsureHostGpuMemoryAllocated(uint32_t start,
|
||||
uint32_t length) {
|
||||
if (!length || !AreTiledResourcesUsed()) {
|
||||
return true;
|
||||
}
|
||||
uint32_t heap_first = start >> kHeapSizeLog2;
|
||||
uint32_t heap_last = (start + length - 1) >> kHeapSizeLog2;
|
||||
assert_true(heap_first < xe::countof(heaps_));
|
||||
assert_true(heap_last < xe::countof(heaps_));
|
||||
for (uint32_t i = heap_first; i <= heap_last; ++i) {
|
||||
if (heaps_[i] != nullptr) {
|
||||
continue;
|
||||
}
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
auto direct_queue = provider.GetDirectQueue();
|
||||
D3D12_HEAP_DESC heap_desc = {};
|
||||
heap_desc.SizeInBytes = kHeapSize;
|
||||
heap_desc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||
heap_desc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS |
|
||||
provider.GetHeapFlagCreateNotZeroed();
|
||||
if (FAILED(device->CreateHeap(&heap_desc, IID_PPV_ARGS(&heaps_[i])))) {
|
||||
XELOGE("Shared memory: Failed to create a tile heap");
|
||||
return false;
|
||||
}
|
||||
++heap_count_;
|
||||
COUNT_profile_set("gpu/shared_memory/used_mb",
|
||||
heap_count_ << kHeapSizeLog2 >> 20);
|
||||
D3D12_TILED_RESOURCE_COORDINATE region_start_coordinates;
|
||||
region_start_coordinates.X =
|
||||
(i << kHeapSizeLog2) / D3D12_TILED_RESOURCE_TILE_SIZE_IN_BYTES;
|
||||
region_start_coordinates.Y = 0;
|
||||
region_start_coordinates.Z = 0;
|
||||
region_start_coordinates.Subresource = 0;
|
||||
D3D12_TILE_REGION_SIZE region_size;
|
||||
region_size.NumTiles = kHeapSize / D3D12_TILED_RESOURCE_TILE_SIZE_IN_BYTES;
|
||||
region_size.UseBox = FALSE;
|
||||
D3D12_TILE_RANGE_FLAGS range_flags = D3D12_TILE_RANGE_FLAG_NONE;
|
||||
UINT heap_range_start_offset = 0;
|
||||
UINT range_tile_count = kHeapSize / D3D12_TILED_RESOURCE_TILE_SIZE_IN_BYTES;
|
||||
direct_queue->UpdateTileMappings(
|
||||
buffer_, 1, ®ion_start_coordinates, ®ion_size, heaps_[i], 1,
|
||||
&range_flags, &heap_range_start_offset, &range_tile_count,
|
||||
D3D12_TILE_MAPPING_FLAG_NONE);
|
||||
command_processor_.NotifyQueueOperationsDoneDirectly();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12SharedMemory::UploadRanges(
|
||||
const std::vector<std::pair<uint32_t, uint32_t>>& upload_page_ranges) {
|
||||
if (upload_page_ranges.empty()) {
|
||||
return true;
|
||||
}
|
||||
CommitUAVWritesAndTransitionBuffer(D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
command_processor_.SubmitBarriers();
|
||||
auto& command_list = command_processor_.GetDeferredCommandList();
|
||||
for (auto upload_range : upload_page_ranges) {
|
||||
uint32_t upload_range_start = upload_range.first;
|
||||
uint32_t upload_range_length = upload_range.second;
|
||||
trace_writer_.WriteMemoryRead(upload_range_start << page_size_log2(),
|
||||
upload_range_length << page_size_log2());
|
||||
while (upload_range_length != 0) {
|
||||
ID3D12Resource* upload_buffer;
|
||||
size_t upload_buffer_offset, upload_buffer_size;
|
||||
uint8_t* upload_buffer_mapping = upload_buffer_pool_->RequestPartial(
|
||||
command_processor_.GetCurrentSubmission(),
|
||||
upload_range_length << page_size_log2(),
|
||||
size_t(1) << page_size_log2(), &upload_buffer, &upload_buffer_offset,
|
||||
&upload_buffer_size, nullptr);
|
||||
if (upload_buffer_mapping == nullptr) {
|
||||
XELOGE("Shared memory: Failed to get an upload buffer");
|
||||
return false;
|
||||
}
|
||||
MakeRangeValid(upload_range_start << page_size_log2(),
|
||||
uint32_t(upload_buffer_size), false);
|
||||
std::memcpy(
|
||||
upload_buffer_mapping,
|
||||
memory().TranslatePhysical(upload_range_start << page_size_log2()),
|
||||
upload_buffer_size);
|
||||
command_list.D3DCopyBufferRegion(
|
||||
buffer_, upload_range_start << page_size_log2(), upload_buffer,
|
||||
UINT64(upload_buffer_offset), UINT64(upload_buffer_size));
|
||||
uint32_t upload_buffer_pages =
|
||||
uint32_t(upload_buffer_size >> page_size_log2());
|
||||
upload_range_start += upload_buffer_pages;
|
||||
upload_range_length -= upload_buffer_pages;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace d3d12
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
Reference in New Issue
Block a user