758 lines
28 KiB
C++
758 lines
28 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2018 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/gpu/d3d12/primitive_converter.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/base/cvar.h"
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/base/math.h"
|
|
#include "xenia/base/memory.h"
|
|
#include "xenia/base/platform.h"
|
|
#include "xenia/base/profiling.h"
|
|
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
|
#include "xenia/ui/d3d12/d3d12_util.h"
|
|
|
|
DEFINE_bool(d3d12_convert_quads_to_triangles, false,
|
|
"Convert quad lists to triangle lists on the CPU instead of using "
|
|
"a geometry shader. Not recommended for playing, for debugging "
|
|
"primarily (because PIX fails to display vertices when a geometry "
|
|
"shader is used).",
|
|
"D3D12");
|
|
|
|
namespace xe {
|
|
namespace gpu {
|
|
namespace d3d12 {
|
|
|
|
constexpr uint32_t PrimitiveConverter::kMaxNonIndexedVertices;
|
|
constexpr uint32_t PrimitiveConverter::kStaticIBTriangleFanOffset;
|
|
constexpr uint32_t PrimitiveConverter::kStaticIBTriangleFanCount;
|
|
constexpr uint32_t PrimitiveConverter::kStaticIBQuadOffset;
|
|
constexpr uint32_t PrimitiveConverter::kStaticIBQuadCount;
|
|
constexpr uint32_t PrimitiveConverter::kStaticIBTotalCount;
|
|
|
|
PrimitiveConverter::PrimitiveConverter(D3D12CommandProcessor* command_processor,
|
|
RegisterFile* register_file,
|
|
Memory* memory,
|
|
TraceWriter* trace_writer)
|
|
: command_processor_(command_processor),
|
|
register_file_(register_file),
|
|
memory_(memory),
|
|
trace_writer_(trace_writer) {
|
|
system_page_size_ = uint32_t(memory::page_size());
|
|
}
|
|
|
|
PrimitiveConverter::~PrimitiveConverter() { Shutdown(); }
|
|
|
|
bool PrimitiveConverter::Initialize() {
|
|
auto context = command_processor_->GetD3D12Context();
|
|
auto device = context->GetD3D12Provider()->GetDevice();
|
|
|
|
// There can be at most 65535 indices in a Xenos draw call, but they can be up
|
|
// to 4 bytes large, and conversion can add more indices (almost triple the
|
|
// count for triangle strips, for instance).
|
|
buffer_pool_ =
|
|
std::make_unique<ui::d3d12::UploadBufferPool>(context, 4 * 1024 * 1024);
|
|
|
|
// Create the static index buffer for non-indexed drawing.
|
|
D3D12_RESOURCE_DESC static_ib_desc;
|
|
ui::d3d12::util::FillBufferResourceDesc(
|
|
static_ib_desc, kStaticIBTotalCount * sizeof(uint16_t),
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
if (FAILED(device->CreateCommittedResource(
|
|
&ui::d3d12::util::kHeapPropertiesUpload, D3D12_HEAP_FLAG_NONE,
|
|
&static_ib_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
|
|
IID_PPV_ARGS(&static_ib_upload_)))) {
|
|
XELOGE(
|
|
"Failed to create the upload buffer for the primitive conversion "
|
|
"static index buffer");
|
|
Shutdown();
|
|
return false;
|
|
}
|
|
D3D12_RANGE static_ib_read_range;
|
|
static_ib_read_range.Begin = 0;
|
|
static_ib_read_range.End = 0;
|
|
void* static_ib_mapping;
|
|
if (FAILED(static_ib_upload_->Map(0, &static_ib_read_range,
|
|
&static_ib_mapping))) {
|
|
XELOGE(
|
|
"Failed to map the upload buffer for the primitive conversion "
|
|
"static index buffer");
|
|
Shutdown();
|
|
return false;
|
|
}
|
|
uint16_t* static_ib_data = reinterpret_cast<uint16_t*>(static_ib_mapping);
|
|
// Triangle fans as triangle lists.
|
|
// https://docs.microsoft.com/en-us/windows/desktop/direct3d9/triangle-fans
|
|
// Ordered as (v1, v2, v0), (v2, v3, v0).
|
|
uint16_t* static_ib_data_pointer =
|
|
&static_ib_data[kStaticIBTriangleFanOffset];
|
|
for (uint32_t i = 2; i < kMaxNonIndexedVertices; ++i) {
|
|
*(static_ib_data_pointer++) = i - 1;
|
|
*(static_ib_data_pointer++) = i;
|
|
*(static_ib_data_pointer++) = 0;
|
|
}
|
|
static_ib_data_pointer = &static_ib_data[kStaticIBQuadOffset];
|
|
for (uint32_t i = 0; i < (kMaxNonIndexedVertices >> 2); ++i) {
|
|
uint32_t quad_index = i << 2;
|
|
*(static_ib_data_pointer++) = quad_index;
|
|
*(static_ib_data_pointer++) = quad_index + 1;
|
|
*(static_ib_data_pointer++) = quad_index + 2;
|
|
*(static_ib_data_pointer++) = quad_index;
|
|
*(static_ib_data_pointer++) = quad_index + 2;
|
|
*(static_ib_data_pointer++) = quad_index + 3;
|
|
}
|
|
static_ib_upload_->Unmap(0, nullptr);
|
|
// Not uploaded yet.
|
|
static_ib_upload_frame_ = UINT64_MAX;
|
|
if (FAILED(device->CreateCommittedResource(
|
|
&ui::d3d12::util::kHeapPropertiesDefault, D3D12_HEAP_FLAG_NONE,
|
|
&static_ib_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
|
|
IID_PPV_ARGS(&static_ib_)))) {
|
|
XELOGE("Failed to create the primitive conversion static index buffer");
|
|
Shutdown();
|
|
return false;
|
|
}
|
|
static_ib_gpu_address_ = static_ib_->GetGPUVirtualAddress();
|
|
|
|
memory_regions_invalidated_.store(0ull, std::memory_order_relaxed);
|
|
physical_write_watch_handle_ =
|
|
memory_->RegisterPhysicalWriteWatch(MemoryWriteCallbackThunk, this);
|
|
|
|
return true;
|
|
}
|
|
|
|
void PrimitiveConverter::Shutdown() {
|
|
if (physical_write_watch_handle_ != nullptr) {
|
|
memory_->UnregisterPhysicalWriteWatch(physical_write_watch_handle_);
|
|
physical_write_watch_handle_ = nullptr;
|
|
}
|
|
ui::d3d12::util::ReleaseAndNull(static_ib_);
|
|
ui::d3d12::util::ReleaseAndNull(static_ib_upload_);
|
|
buffer_pool_.reset();
|
|
}
|
|
|
|
void PrimitiveConverter::ClearCache() { buffer_pool_->ClearCache(); }
|
|
|
|
void PrimitiveConverter::BeginFrame() {
|
|
// Got a command list now - upload and transition the static index buffer if
|
|
// needed.
|
|
if (static_ib_upload_ != nullptr) {
|
|
auto context = command_processor_->GetD3D12Context();
|
|
if (static_ib_upload_frame_ == UINT64_MAX) {
|
|
// Not uploaded yet - upload.
|
|
command_processor_->GetDeferredCommandList()->D3DCopyResource(
|
|
static_ib_, static_ib_upload_);
|
|
command_processor_->PushTransitionBarrier(
|
|
static_ib_, D3D12_RESOURCE_STATE_COPY_DEST,
|
|
D3D12_RESOURCE_STATE_INDEX_BUFFER);
|
|
static_ib_upload_frame_ = context->GetCurrentFrame();
|
|
} else if (context->GetLastCompletedFrame() >= static_ib_upload_frame_) {
|
|
// Completely uploaded - release the upload buffer.
|
|
static_ib_upload_->Release();
|
|
static_ib_upload_ = nullptr;
|
|
}
|
|
}
|
|
|
|
buffer_pool_->BeginFrame();
|
|
|
|
converted_indices_cache_.clear();
|
|
memory_regions_used_ = 0;
|
|
}
|
|
|
|
void PrimitiveConverter::EndFrame() { buffer_pool_->EndFrame(); }
|
|
|
|
PrimitiveType PrimitiveConverter::GetReplacementPrimitiveType(
|
|
PrimitiveType type) {
|
|
switch (type) {
|
|
case PrimitiveType::kTriangleFan:
|
|
return PrimitiveType::kTriangleList;
|
|
case PrimitiveType::kLineLoop:
|
|
return PrimitiveType::kLineStrip;
|
|
case PrimitiveType::kQuadList:
|
|
if (cvars::d3d12_convert_quads_to_triangles) {
|
|
return PrimitiveType::kTriangleList;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
PrimitiveConverter::ConversionResult PrimitiveConverter::ConvertPrimitives(
|
|
PrimitiveType source_type, uint32_t address, uint32_t index_count,
|
|
IndexFormat index_format, Endian index_endianness,
|
|
D3D12_GPU_VIRTUAL_ADDRESS& gpu_address_out, uint32_t& index_count_out) {
|
|
bool index_32bit = index_format == IndexFormat::kInt32;
|
|
auto& regs = *register_file_;
|
|
bool reset = regs.Get<reg::PA_SU_SC_MODE_CNTL>().multi_prim_ib_ena;
|
|
// Swap the reset index because we will be comparing unswapped values to it.
|
|
uint32_t reset_index = xenos::GpuSwap(
|
|
regs[XE_GPU_REG_VGT_MULTI_PRIM_IB_RESET_INDX].u32, index_endianness);
|
|
// If the specified reset index is the same as the one used by Direct3D 12
|
|
// (0xFFFF or 0xFFFFFFFF - in the pipeline cache, we use the former for
|
|
// 16-bit and the latter for 32-bit indices), we can use the buffer directly.
|
|
uint32_t reset_index_host = index_32bit ? 0xFFFFFFFFu : 0xFFFFu;
|
|
|
|
// Degenerate line loops are just lines.
|
|
if (source_type == PrimitiveType::kLineLoop && index_count <= 2) {
|
|
source_type = PrimitiveType::kLineStrip;
|
|
}
|
|
|
|
// Check if need to convert at all.
|
|
if (source_type == PrimitiveType::kTriangleStrip ||
|
|
source_type == PrimitiveType::kLineStrip) {
|
|
if (!reset || reset_index == reset_index_host) {
|
|
return ConversionResult::kConversionNotNeeded;
|
|
}
|
|
} else if (source_type == PrimitiveType::kQuadList) {
|
|
if (!cvars::d3d12_convert_quads_to_triangles) {
|
|
return ConversionResult::kConversionNotNeeded;
|
|
}
|
|
} else if (source_type != PrimitiveType::kTriangleFan &&
|
|
source_type != PrimitiveType::kLineLoop) {
|
|
return ConversionResult::kConversionNotNeeded;
|
|
}
|
|
|
|
#if FINE_GRAINED_DRAW_SCOPES
|
|
SCOPE_profile_cpu_f("gpu");
|
|
#endif // FINE_GRAINED_DRAW_SCOPES
|
|
|
|
// Exit early for clearly empty draws, without even reading the memory.
|
|
uint32_t index_count_min;
|
|
if (source_type == PrimitiveType::kLineStrip ||
|
|
source_type == PrimitiveType::kLineLoop) {
|
|
index_count_min = 2;
|
|
} else if (source_type == PrimitiveType::kQuadList) {
|
|
index_count_min = 4;
|
|
} else {
|
|
index_count_min = 3;
|
|
}
|
|
if (index_count < index_count_min) {
|
|
return ConversionResult::kPrimitiveEmpty;
|
|
}
|
|
|
|
// Invalidate the cache if data behind any entry was modified.
|
|
if (memory_regions_invalidated_.exchange(0ull, std::memory_order_acquire) &
|
|
memory_regions_used_) {
|
|
converted_indices_cache_.clear();
|
|
memory_regions_used_ = 0;
|
|
}
|
|
|
|
address &= index_32bit ? 0x1FFFFFFC : 0x1FFFFFFE;
|
|
uint32_t index_size = index_32bit ? sizeof(uint32_t) : sizeof(uint16_t);
|
|
uint32_t index_buffer_size = index_size * index_count;
|
|
uint32_t address_last = address + index_size * (index_count - 1);
|
|
|
|
// Create the cache entry, currently only for the key.
|
|
ConvertedIndices converted_indices;
|
|
converted_indices.key.address = address;
|
|
converted_indices.key.source_type = source_type;
|
|
converted_indices.key.format = index_format;
|
|
converted_indices.key.count = index_count;
|
|
converted_indices.key.reset = reset ? 1 : 0;
|
|
converted_indices.reset_index = reset_index;
|
|
|
|
// Try to find the previously converted index buffer.
|
|
auto found_range =
|
|
converted_indices_cache_.equal_range(converted_indices.key.value);
|
|
for (auto iter = found_range.first; iter != found_range.second; ++iter) {
|
|
const ConvertedIndices& found_converted = iter->second;
|
|
if (reset && found_converted.reset_index != reset_index) {
|
|
continue;
|
|
}
|
|
if (found_converted.converted_index_count == 0) {
|
|
return ConversionResult::kPrimitiveEmpty;
|
|
}
|
|
if (!found_converted.gpu_address) {
|
|
return ConversionResult::kConversionNotNeeded;
|
|
}
|
|
gpu_address_out = found_converted.gpu_address;
|
|
index_count_out = found_converted.converted_index_count;
|
|
return ConversionResult::kConverted;
|
|
}
|
|
|
|
// Get the memory usage mask for cache invalidation.
|
|
// 1 bit = (512 / 64) MB = 8 MB.
|
|
uint64_t memory_regions_used_bits = ~((1ull << (address >> 23)) - 1);
|
|
if (address_last < (63 << 23)) {
|
|
memory_regions_used_bits = (1ull << ((address_last >> 23) + 1)) - 1;
|
|
}
|
|
|
|
union {
|
|
const void* source;
|
|
const uint8_t* source_8;
|
|
const uint16_t* source_16;
|
|
const uint32_t* source_32;
|
|
uintptr_t source_uintptr;
|
|
};
|
|
source = memory_->TranslatePhysical(address);
|
|
|
|
// Calculate the new index count, and also check if there's nothing to convert
|
|
// in the buffer (for instance, if not using actually primitive reset).
|
|
uint32_t converted_index_count = 0;
|
|
bool conversion_needed = false;
|
|
bool simd = false;
|
|
// Optimization specific to primitive types - if reset index not found in the
|
|
// source index buffer, can set this to false and use a faster way of copying.
|
|
bool reset_actually_used = reset;
|
|
if (source_type == PrimitiveType::kTriangleFan) {
|
|
// Triangle fans are not supported by Direct3D 12 at all.
|
|
conversion_needed = true;
|
|
trace_writer_->WriteMemoryRead(address, index_buffer_size);
|
|
if (reset) {
|
|
uint32_t current_fan_index_count = 0;
|
|
for (uint32_t i = 0; i < index_count; ++i) {
|
|
uint32_t index =
|
|
index_format == IndexFormat::kInt32 ? source_32[i] : source_16[i];
|
|
if (index == reset_index) {
|
|
current_fan_index_count = 0;
|
|
continue;
|
|
}
|
|
if (++current_fan_index_count >= 3) {
|
|
converted_index_count += 3;
|
|
}
|
|
}
|
|
} else {
|
|
converted_index_count = 3 * (index_count - 2);
|
|
}
|
|
} else if (source_type == PrimitiveType::kTriangleStrip ||
|
|
source_type == PrimitiveType::kLineStrip) {
|
|
converted_index_count = index_count;
|
|
// Check if the restart index is used at all in this buffer because reading
|
|
// vertices from a default heap is faster than from an upload heap.
|
|
conversion_needed = false;
|
|
trace_writer_->WriteMemoryRead(address, index_buffer_size);
|
|
#if XE_ARCH_AMD64
|
|
// Will use SIMD to copy 16-byte blocks using _mm_or_si128.
|
|
simd = true;
|
|
union {
|
|
const void* check_source;
|
|
const uint32_t* check_source_16;
|
|
const uint32_t* check_source_32;
|
|
const __m128i* check_source_128;
|
|
uintptr_t check_source_uintptr;
|
|
};
|
|
check_source = source;
|
|
uint32_t check_indices_remaining = index_count;
|
|
alignas(16) uint64_t check_result[2];
|
|
if (index_format == IndexFormat::kInt32) {
|
|
while (check_indices_remaining != 0 && (check_source_uintptr & 15)) {
|
|
--check_indices_remaining;
|
|
if (*(check_source_32++) == reset_index) {
|
|
conversion_needed = true;
|
|
check_indices_remaining = 0;
|
|
}
|
|
}
|
|
__m128i check_reset_index_vector = _mm_set1_epi32(reset_index);
|
|
while (check_indices_remaining >= 4) {
|
|
check_indices_remaining -= 4;
|
|
_mm_store_si128(reinterpret_cast<__m128i*>(&check_result),
|
|
_mm_cmpeq_epi32(_mm_load_si128(check_source_128++),
|
|
check_reset_index_vector));
|
|
if (check_result[0] || check_result[1]) {
|
|
conversion_needed = true;
|
|
check_indices_remaining = 0;
|
|
}
|
|
}
|
|
while (check_indices_remaining != 0) {
|
|
--check_indices_remaining;
|
|
if (*(check_source_32++) == reset_index) {
|
|
conversion_needed = true;
|
|
check_indices_remaining = 0;
|
|
}
|
|
}
|
|
} else {
|
|
while (check_indices_remaining != 0 && (check_source_uintptr & 15)) {
|
|
--check_indices_remaining;
|
|
if (*(check_source_16++) == reset_index) {
|
|
conversion_needed = true;
|
|
check_indices_remaining = 0;
|
|
}
|
|
}
|
|
__m128i check_reset_index_vector = _mm_set1_epi16(reset_index);
|
|
while (check_indices_remaining >= 8) {
|
|
check_indices_remaining -= 8;
|
|
_mm_store_si128(reinterpret_cast<__m128i*>(&check_result),
|
|
_mm_cmpeq_epi16(_mm_load_si128(check_source_128++),
|
|
check_reset_index_vector));
|
|
if (check_result[0] || check_result[1]) {
|
|
conversion_needed = true;
|
|
check_indices_remaining = 0;
|
|
}
|
|
}
|
|
while (check_indices_remaining != 0) {
|
|
--check_indices_remaining;
|
|
if (*(check_source_16++) == reset_index) {
|
|
conversion_needed = true;
|
|
check_indices_remaining = 0;
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
if (index_format == IndexFormat::kInt32) {
|
|
for (uint32_t i = 0; i < index_count; ++i) {
|
|
if (source_32[i] == reset_index) {
|
|
conversion_needed = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for (uint32_t i = 0; i < index_count; ++i) {
|
|
if (source_16[i] == reset_index) {
|
|
conversion_needed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif // XE_ARCH_AMD64
|
|
} else if (source_type == PrimitiveType::kLineLoop) {
|
|
conversion_needed = true;
|
|
trace_writer_->WriteMemoryRead(address, index_buffer_size);
|
|
if (reset) {
|
|
reset_actually_used = false;
|
|
uint32_t current_strip_index_count = 0;
|
|
for (uint32_t i = 0; i < index_count; ++i) {
|
|
uint32_t index =
|
|
index_format == IndexFormat::kInt32 ? source_32[i] : source_16[i];
|
|
if (index == reset_index) {
|
|
reset_actually_used = true;
|
|
// Loop strips with more than 2 vertices.
|
|
if (current_strip_index_count > 2) {
|
|
++converted_index_count;
|
|
}
|
|
current_strip_index_count = 0;
|
|
continue;
|
|
}
|
|
// Start a new strip if 2 vertices, add one vertex if more.
|
|
if (++current_strip_index_count >= 2) {
|
|
converted_index_count += current_strip_index_count == 2 ? 2 : 1;
|
|
}
|
|
}
|
|
} else {
|
|
converted_index_count = index_count + 1;
|
|
}
|
|
} else if (source_type == PrimitiveType::kQuadList) {
|
|
conversion_needed = true;
|
|
trace_writer_->WriteMemoryRead(address, index_buffer_size);
|
|
converted_index_count = (index_count >> 2) * 6;
|
|
}
|
|
converted_indices.converted_index_count = converted_index_count;
|
|
|
|
// If nothing to convert, store this result so the check won't be happening
|
|
// again and again and exit.
|
|
if (!conversion_needed || converted_index_count == 0) {
|
|
converted_indices.gpu_address = 0;
|
|
converted_indices_cache_.insert(
|
|
std::make_pair(converted_indices.key.value, converted_indices));
|
|
memory_regions_used_ |= memory_regions_used_bits;
|
|
return converted_index_count == 0 ? ConversionResult::kPrimitiveEmpty
|
|
: ConversionResult::kConversionNotNeeded;
|
|
}
|
|
|
|
// Convert.
|
|
|
|
D3D12_GPU_VIRTUAL_ADDRESS gpu_address;
|
|
void* target = AllocateIndices(index_format, converted_index_count,
|
|
simd ? address & 15 : 0, gpu_address);
|
|
if (target == nullptr) {
|
|
return ConversionResult::kFailed;
|
|
}
|
|
|
|
if (source_type == PrimitiveType::kTriangleFan) {
|
|
// https://docs.microsoft.com/en-us/windows/desktop/direct3d9/triangle-fans
|
|
// Ordered as (v1, v2, v0), (v2, v3, v0).
|
|
if (reset) {
|
|
uint32_t current_fan_index_count = 0;
|
|
uint32_t current_fan_first_index = 0;
|
|
if (index_format == IndexFormat::kInt32) {
|
|
uint32_t* target_32 = reinterpret_cast<uint32_t*>(target);
|
|
for (uint32_t i = 0; i < index_count; ++i) {
|
|
uint32_t index = source_32[i];
|
|
if (index == reset_index) {
|
|
current_fan_index_count = 0;
|
|
continue;
|
|
}
|
|
if (current_fan_index_count == 0) {
|
|
current_fan_first_index = index;
|
|
}
|
|
if (++current_fan_index_count >= 3) {
|
|
*(target_32++) = source_32[i - 1];
|
|
*(target_32++) = index;
|
|
*(target_32++) = current_fan_first_index;
|
|
}
|
|
}
|
|
} else {
|
|
uint16_t* target_16 = reinterpret_cast<uint16_t*>(target);
|
|
for (uint32_t i = 0; i < index_count; ++i) {
|
|
uint16_t index = source_16[i];
|
|
if (index == reset_index) {
|
|
current_fan_index_count = 0;
|
|
continue;
|
|
}
|
|
if (current_fan_index_count == 0) {
|
|
current_fan_first_index = index;
|
|
}
|
|
if (++current_fan_index_count >= 3) {
|
|
*(target_16++) = source_16[i - 1];
|
|
*(target_16++) = index;
|
|
*(target_16++) = uint16_t(current_fan_first_index);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (index_format == IndexFormat::kInt32) {
|
|
uint32_t* target_32 = reinterpret_cast<uint32_t*>(target);
|
|
for (uint32_t i = 2; i < index_count; ++i) {
|
|
*(target_32++) = source_32[i - 1];
|
|
*(target_32++) = source_32[i];
|
|
*(target_32++) = source_32[0];
|
|
}
|
|
} else {
|
|
uint16_t* target_16 = reinterpret_cast<uint16_t*>(target);
|
|
for (uint32_t i = 2; i < index_count; ++i) {
|
|
*(target_16++) = source_16[i - 1];
|
|
*(target_16++) = source_16[i];
|
|
*(target_16++) = source_16[0];
|
|
}
|
|
}
|
|
}
|
|
} else if (source_type == PrimitiveType::kTriangleStrip ||
|
|
source_type == PrimitiveType::kLineStrip) {
|
|
#if XE_ARCH_AMD64
|
|
// Replace the reset index with the maximum representable value - vector OR
|
|
// gives 0 or 0xFFFF/0xFFFFFFFF, which is exactly what is needed.
|
|
// Allocations in the target index buffer are aligned with 16-byte
|
|
// granularity, and within 16-byte vectors, both the source and the target
|
|
// start at the same offset.
|
|
union {
|
|
const __m128i* source_aligned_128;
|
|
uintptr_t source_aligned_uintptr;
|
|
};
|
|
source_aligned_uintptr = source_uintptr & ~(uintptr_t(15));
|
|
union {
|
|
__m128i* target_aligned_128;
|
|
uintptr_t target_aligned_uintptr;
|
|
};
|
|
target_aligned_uintptr =
|
|
reinterpret_cast<uintptr_t>(target) & ~(uintptr_t(15));
|
|
uint32_t vector_count = (address_last >> 4) - (address >> 4) + 1;
|
|
if (index_format == IndexFormat::kInt32) {
|
|
__m128i reset_index_vector = _mm_set1_epi32(reset_index);
|
|
for (uint32_t i = 0; i < vector_count; ++i) {
|
|
__m128i indices_vector = _mm_load_si128(source_aligned_128++);
|
|
__m128i indices_are_reset_vector =
|
|
_mm_cmpeq_epi32(indices_vector, reset_index_vector);
|
|
_mm_store_si128(target_aligned_128++,
|
|
_mm_or_si128(indices_vector, indices_are_reset_vector));
|
|
}
|
|
} else {
|
|
__m128i reset_index_vector = _mm_set1_epi16(reset_index);
|
|
for (uint32_t i = 0; i < vector_count; ++i) {
|
|
__m128i indices_vector = _mm_load_si128(source_aligned_128++);
|
|
__m128i indices_are_reset_vector =
|
|
_mm_cmpeq_epi16(indices_vector, reset_index_vector);
|
|
_mm_store_si128(target_aligned_128++,
|
|
_mm_or_si128(indices_vector, indices_are_reset_vector));
|
|
}
|
|
}
|
|
#else
|
|
if (index_format == IndexFormat::kInt32) {
|
|
for (uint32_t i = 0; i < index_count; ++i) {
|
|
uint32_t index = source_32[i];
|
|
reinterpret_cast<uint32_t*>(target)[i] =
|
|
index == reset_index ? 0xFFFFFFFFu : index;
|
|
}
|
|
} else {
|
|
for (uint32_t i = 0; i < index_count; ++i) {
|
|
uint16_t index = source_16[i];
|
|
reinterpret_cast<uint16_t*>(target)[i] =
|
|
index == reset_index ? 0xFFFFu : index;
|
|
}
|
|
}
|
|
#endif // XE_ARCH_AMD64
|
|
} else if (source_type == PrimitiveType::kLineLoop) {
|
|
if (reset_actually_used) {
|
|
uint32_t current_strip_index_count = 0;
|
|
uint32_t current_strip_first_index = 0;
|
|
if (index_format == IndexFormat::kInt32) {
|
|
uint32_t* target_32 = reinterpret_cast<uint32_t*>(target);
|
|
for (uint32_t i = 0; i < index_count; ++i) {
|
|
uint32_t index = source_32[i];
|
|
if (index == reset_index) {
|
|
if (current_strip_index_count > 2) {
|
|
*(target_32++) = current_strip_first_index;
|
|
}
|
|
current_strip_index_count = 0;
|
|
continue;
|
|
}
|
|
if (current_strip_index_count == 0) {
|
|
current_strip_first_index = index;
|
|
}
|
|
++current_strip_index_count;
|
|
if (current_strip_index_count >= 2) {
|
|
if (current_strip_index_count == 2) {
|
|
*(target_32++) = current_strip_first_index;
|
|
}
|
|
*(target_32++) = index;
|
|
}
|
|
}
|
|
} else {
|
|
uint16_t* target_16 = reinterpret_cast<uint16_t*>(target);
|
|
for (uint32_t i = 0; i < index_count; ++i) {
|
|
uint16_t index = source_16[i];
|
|
if (index == reset_index) {
|
|
if (current_strip_index_count > 2) {
|
|
*(target_16++) = uint16_t(current_strip_first_index);
|
|
}
|
|
current_strip_index_count = 0;
|
|
continue;
|
|
}
|
|
if (current_strip_index_count == 0) {
|
|
current_strip_first_index = index;
|
|
}
|
|
++current_strip_index_count;
|
|
if (current_strip_index_count >= 2) {
|
|
if (current_strip_index_count == 2) {
|
|
*(target_16++) = uint16_t(current_strip_first_index);
|
|
}
|
|
*(target_16++) = index;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
std::memcpy(target, source, index_count * index_size);
|
|
if (converted_index_count > index_count) {
|
|
if (index_format == IndexFormat::kInt32) {
|
|
reinterpret_cast<uint32_t*>(target)[index_count] = source_32[0];
|
|
} else {
|
|
reinterpret_cast<uint16_t*>(target)[index_count] = source_16[0];
|
|
}
|
|
}
|
|
}
|
|
} else if (source_type == PrimitiveType::kQuadList) {
|
|
uint32_t quad_count = index_count >> 4;
|
|
if (index_format == IndexFormat::kInt32) {
|
|
uint32_t* target_32 = reinterpret_cast<uint32_t*>(target);
|
|
for (uint32_t i = 0; i < quad_count; ++i) {
|
|
uint32_t quad_index = i << 2;
|
|
*(target_32++) = source_32[quad_index];
|
|
*(target_32++) = source_32[quad_index + 1];
|
|
*(target_32++) = source_32[quad_index + 2];
|
|
*(target_32++) = source_32[quad_index];
|
|
*(target_32++) = source_32[quad_index + 2];
|
|
*(target_32++) = source_32[quad_index + 3];
|
|
}
|
|
} else {
|
|
uint16_t* target_16 = reinterpret_cast<uint16_t*>(target);
|
|
for (uint32_t i = 0; i < quad_count; ++i) {
|
|
uint32_t quad_index = i << 2;
|
|
*(target_16++) = source_16[quad_index];
|
|
*(target_16++) = source_16[quad_index + 1];
|
|
*(target_16++) = source_16[quad_index + 2];
|
|
*(target_16++) = source_16[quad_index];
|
|
*(target_16++) = source_16[quad_index + 2];
|
|
*(target_16++) = source_16[quad_index + 3];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cache and return the indices.
|
|
converted_indices.gpu_address = gpu_address;
|
|
converted_indices_cache_.insert(
|
|
std::make_pair(converted_indices.key.value, converted_indices));
|
|
memory_regions_used_ |= memory_regions_used_bits;
|
|
gpu_address_out = gpu_address;
|
|
index_count_out = converted_index_count;
|
|
return ConversionResult::kConverted;
|
|
}
|
|
|
|
void* PrimitiveConverter::AllocateIndices(
|
|
IndexFormat format, uint32_t count, uint32_t simd_offset,
|
|
D3D12_GPU_VIRTUAL_ADDRESS& gpu_address_out) {
|
|
if (count == 0) {
|
|
return nullptr;
|
|
}
|
|
uint32_t size = count * (format == IndexFormat::kInt32 ? sizeof(uint32_t)
|
|
: sizeof(uint16_t));
|
|
// 16-align all index data because SIMD is used to replace the reset index
|
|
// (without that, 4-alignment would be required anyway to mix 16-bit and
|
|
// 32-bit indices in one buffer page).
|
|
size = xe::align(size, uint32_t(16));
|
|
// Add some space to align SIMD register components the same way in the source
|
|
// and the buffer.
|
|
simd_offset &= 15;
|
|
if (simd_offset != 0) {
|
|
size += 16;
|
|
}
|
|
D3D12_GPU_VIRTUAL_ADDRESS gpu_address;
|
|
uint8_t* mapping =
|
|
buffer_pool_->RequestFull(size, nullptr, nullptr, &gpu_address);
|
|
if (mapping == nullptr) {
|
|
XELOGE("Failed to allocate space for %u converted %u-bit vertex indices",
|
|
count, format == IndexFormat::kInt32 ? 32 : 16);
|
|
return nullptr;
|
|
}
|
|
gpu_address_out = gpu_address + simd_offset;
|
|
return mapping + simd_offset;
|
|
}
|
|
|
|
std::pair<uint32_t, uint32_t> PrimitiveConverter::MemoryWriteCallback(
|
|
uint32_t physical_address_start, uint32_t length, bool exact_range) {
|
|
// 1 bit = (512 / 64) MB = 8 MB. Invalidate a region of this size.
|
|
uint32_t bit_index_first = physical_address_start >> 23;
|
|
uint32_t bit_index_last = (physical_address_start + length - 1) >> 23;
|
|
uint64_t bits = ~((1ull << bit_index_first) - 1);
|
|
if (bit_index_last < 63) {
|
|
bits &= (1ull << (bit_index_last + 1)) - 1;
|
|
}
|
|
memory_regions_invalidated_ |= bits;
|
|
return std::make_pair<uint32_t, uint32_t>(0, UINT32_MAX);
|
|
}
|
|
|
|
std::pair<uint32_t, uint32_t> PrimitiveConverter::MemoryWriteCallbackThunk(
|
|
void* context_ptr, uint32_t physical_address_start, uint32_t length,
|
|
bool exact_range) {
|
|
return reinterpret_cast<PrimitiveConverter*>(context_ptr)
|
|
->MemoryWriteCallback(physical_address_start, length, exact_range);
|
|
}
|
|
|
|
D3D12_GPU_VIRTUAL_ADDRESS PrimitiveConverter::GetStaticIndexBuffer(
|
|
PrimitiveType source_type, uint32_t index_count,
|
|
uint32_t& index_count_out) const {
|
|
if (index_count > kMaxNonIndexedVertices) {
|
|
assert_always();
|
|
return D3D12_GPU_VIRTUAL_ADDRESS(0);
|
|
}
|
|
if (source_type == PrimitiveType::kTriangleFan) {
|
|
index_count_out = (std::max(index_count, uint32_t(2)) - 2) * 3;
|
|
return static_ib_gpu_address_ +
|
|
kStaticIBTriangleFanOffset * sizeof(uint16_t);
|
|
}
|
|
if (source_type == PrimitiveType::kQuadList &&
|
|
cvars::d3d12_convert_quads_to_triangles) {
|
|
index_count_out = (index_count >> 2) * 6;
|
|
return static_ib_gpu_address_ + kStaticIBQuadOffset * sizeof(uint16_t);
|
|
}
|
|
return D3D12_GPU_VIRTUAL_ADDRESS(0);
|
|
}
|
|
|
|
void PrimitiveConverter::InitializeTrace() {
|
|
// WriteMemoryRead must not be skipped.
|
|
converted_indices_cache_.clear();
|
|
memory_regions_used_ = 0;
|
|
}
|
|
|
|
} // namespace d3d12
|
|
} // namespace gpu
|
|
} // namespace xe
|