[D3D12] Primitive converter cache and strip restart, texture invalidation acquire/release
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
||||
#include "xenia/ui/d3d12/d3d12_util.h"
|
||||
@@ -24,12 +25,12 @@ namespace d3d12 {
|
||||
|
||||
PrimitiveConverter::PrimitiveConverter(D3D12CommandProcessor* command_processor,
|
||||
RegisterFile* register_file,
|
||||
Memory* memory,
|
||||
SharedMemory* shared_memory)
|
||||
Memory* memory)
|
||||
: command_processor_(command_processor),
|
||||
register_file_(register_file),
|
||||
memory_(memory),
|
||||
shared_memory_(shared_memory) {}
|
||||
memory_(memory) {
|
||||
system_page_size_ = uint32_t(memory::page_size());
|
||||
}
|
||||
|
||||
PrimitiveConverter::~PrimitiveConverter() { Shutdown(); }
|
||||
|
||||
@@ -94,10 +95,18 @@ bool PrimitiveConverter::Initialize() {
|
||||
}
|
||||
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();
|
||||
@@ -106,8 +115,6 @@ void PrimitiveConverter::Shutdown() {
|
||||
void PrimitiveConverter::ClearCache() { buffer_pool_->ClearCache(); }
|
||||
|
||||
void PrimitiveConverter::BeginFrame() {
|
||||
buffer_pool_->BeginFrame();
|
||||
|
||||
// Got a command list now - upload and transition the static index buffer if
|
||||
// needed.
|
||||
if (static_ib_upload_ != nullptr) {
|
||||
@@ -126,6 +133,11 @@ void PrimitiveConverter::BeginFrame() {
|
||||
static_ib_upload_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
buffer_pool_->BeginFrame();
|
||||
|
||||
converted_indices_cache_.clear();
|
||||
memory_regions_used_ = 0;
|
||||
}
|
||||
|
||||
void PrimitiveConverter::EndFrame() { buffer_pool_->EndFrame(); }
|
||||
@@ -142,6 +154,7 @@ 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[XE_GPU_REG_PA_SU_SC_MODE_CNTL].u32 & (1 << 21)) != 0;
|
||||
// Swap the reset index because we will be comparing unswapped values to it.
|
||||
@@ -150,40 +163,79 @@ PrimitiveConverter::ConversionResult PrimitiveConverter::ConvertPrimitives(
|
||||
// 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_format == IndexFormat::kInt32 ? 0xFFFFFFFFu : 0xFFFFu;
|
||||
uint32_t reset_index_host = index_32bit ? 0xFFFFFFFFu : 0xFFFFu;
|
||||
|
||||
// Check if need to convert at all.
|
||||
if (source_type != PrimitiveType::kTriangleFan) {
|
||||
if (!reset || reset_index == reset_index_host) {
|
||||
return ConversionResult::kConversionNotNeeded;
|
||||
}
|
||||
if (source_type != PrimitiveType::kTriangleStrip ||
|
||||
if (source_type != PrimitiveType::kTriangleStrip &&
|
||||
source_type != PrimitiveType::kLineStrip) {
|
||||
return ConversionResult::kConversionNotNeeded;
|
||||
}
|
||||
// TODO(Triang3l): Write conversion for triangle and line strip reset index
|
||||
// and for indexed line loops.
|
||||
return ConversionResult::kConversionNotNeeded;
|
||||
}
|
||||
|
||||
// Exit early for clearly empty draws, without even reading the memory.
|
||||
if (source_type == PrimitiveType::kTriangleFan ||
|
||||
source_type == PrimitiveType::kTriangleStrip) {
|
||||
if (index_count < 3) {
|
||||
return ConversionResult::kPrimitiveEmpty;
|
||||
}
|
||||
} else if (source_type == PrimitiveType::kLineStrip ||
|
||||
source_type == PrimitiveType::kLineLoop) {
|
||||
if (index_count < 2) {
|
||||
return ConversionResult::kPrimitiveEmpty;
|
||||
}
|
||||
uint32_t index_count_min;
|
||||
if (source_type == PrimitiveType::kLineStrip ||
|
||||
source_type == PrimitiveType::kLineLoop) {
|
||||
index_count_min = 2;
|
||||
} else {
|
||||
index_count_min = 3;
|
||||
}
|
||||
if (index_count < index_count_min) {
|
||||
return ConversionResult::kPrimitiveEmpty;
|
||||
}
|
||||
|
||||
// TODO(Triang3l): Find the converted data in the cache.
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Calculate the index count, and also check if there's nothing to convert in
|
||||
// the buffer (for instance, if not using primitive reset).
|
||||
address &= index_32bit ? 0x1FFFFFFC : 0x1FFFFFFE;
|
||||
uint32_t index_size = index_32bit ? sizeof(uint32_t) : sizeof(uint16_t);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -196,22 +248,44 @@ PrimitiveConverter::ConversionResult PrimitiveConverter::ConvertPrimitives(
|
||||
} else {
|
||||
converted_index_count = 3 * (index_count - 2);
|
||||
}
|
||||
} else if (source_type == PrimitiveType::kTriangleStrip ||
|
||||
source_type == PrimitiveType::kLineStrip) {
|
||||
// TODO(Triang3l): Check if the restart index is used at all in this buffer.
|
||||
conversion_needed = true;
|
||||
converted_index_count = index_count;
|
||||
simd = true;
|
||||
}
|
||||
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.
|
||||
|
||||
union {
|
||||
void* source;
|
||||
uint16_t* source_16;
|
||||
uint32_t* source_32;
|
||||
const void* source;
|
||||
const uint8_t* source_8;
|
||||
const uint16_t* source_16;
|
||||
const uint32_t* source_32;
|
||||
};
|
||||
source = memory_->TranslatePhysical(address);
|
||||
union {
|
||||
void* target;
|
||||
uint8_t* target_8;
|
||||
uint16_t* target_16;
|
||||
uint32_t* target_32;
|
||||
};
|
||||
D3D12_GPU_VIRTUAL_ADDRESS gpu_address;
|
||||
target = AllocateIndices(index_format, index_count, simd ? address & 15 : 0,
|
||||
gpu_address);
|
||||
target = AllocateIndices(index_format, converted_index_count,
|
||||
simd ? address & 15 : 0, gpu_address);
|
||||
if (target == nullptr) {
|
||||
return ConversionResult::kFailed;
|
||||
}
|
||||
@@ -237,11 +311,62 @@ PrimitiveConverter::ConversionResult PrimitiveConverter::ConvertPrimitives(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (source_type == PrimitiveType::kTriangleStrip ||
|
||||
source_type == PrimitiveType::kLineStrip) {
|
||||
// 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.
|
||||
#if XE_ARCH_AMD64
|
||||
source = reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(source) &
|
||||
~(uintptr_t(15)));
|
||||
target = reinterpret_cast<void*>(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(reinterpret_cast<const __m128i*>(&source_8[i << 4]));
|
||||
__m128i indices_are_reset_vector =
|
||||
_mm_cmpeq_epi32(indices_vector, reset_index_vector);
|
||||
_mm_store_si128(reinterpret_cast<__m128i*>(&target_8[i << 4]),
|
||||
_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(reinterpret_cast<const __m128i*>(&source_8[i << 4]));
|
||||
__m128i indices_are_reset_vector =
|
||||
_mm_cmpeq_epi16(indices_vector, reset_index_vector);
|
||||
_mm_store_si128(reinterpret_cast<__m128i*>(&target_8[i << 4]),
|
||||
_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];
|
||||
target_32[i] = index == reset_index ? 0xFFFFFFFFu : index;
|
||||
}
|
||||
} else {
|
||||
for (uint32_t i = 0; i < index_count; ++i) {
|
||||
uint16_t index = source_16[i];
|
||||
target_16[i] = index == reset_index ? 0xFFFFu : index;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// TODO(Triang3l): Replace primitive reset index in triangle and line strips.
|
||||
// TODO(Triang3l): Line loops.
|
||||
|
||||
// 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;
|
||||
@@ -277,6 +402,25 @@ void* PrimitiveConverter::AllocateIndices(
|
||||
return mapping + simd_offset;
|
||||
}
|
||||
|
||||
void PrimitiveConverter::MemoryWriteCallback(uint32_t page_first,
|
||||
uint32_t page_last) {
|
||||
// 1 bit = (512 / 64) MB = 8 MB. Invalidate a region of this size.
|
||||
uint32_t bit_index_first = (page_first * system_page_size_) >> 23;
|
||||
uint32_t bit_index_last = (page_last * system_page_size_) >> 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;
|
||||
}
|
||||
|
||||
void PrimitiveConverter::MemoryWriteCallbackThunk(void* context_ptr,
|
||||
uint32_t page_first,
|
||||
uint32_t page_last) {
|
||||
reinterpret_cast<PrimitiveConverter*>(context_ptr)
|
||||
->MemoryWriteCallback(page_first, page_last);
|
||||
}
|
||||
|
||||
D3D12_GPU_VIRTUAL_ADDRESS PrimitiveConverter::GetStaticIndexBuffer(
|
||||
PrimitiveType source_type, uint32_t index_count,
|
||||
uint32_t& index_count_out) const {
|
||||
|
||||
Reference in New Issue
Block a user