add xe::clear_lowest_bit, use it in place of shift-andnot in some bit iteration code make is_allocated_ and is_enabled_ volatile in xma_context preallocate avpacket buffer in XMAContext::Setup, the reallocations of the buffer in ffmpeg were showing up on profiles check is_enabled and is_allocated BEFORE locking an xmacontext. XMA worker was spending most of its time locking and unlocking contexts Removed XeDMAC, dma:: namespace. It was a bad idea and I couldn't make it work in the end. Kept vastcpy and moved it to the memory namespace instead Made the rest of global_critical_region's members static. They never needed an instance. Removed ifdef'ed out code from ring_buffer.h Added EventInfo struct to threading, added Event::Query to aid with implementing NtQueryEvent. Removed vector from WaitMultiple, instead use a fixed array of 64 handles that we populate. WaitForMultipleObjects cannot handle more than 64 objects. Remove XE_MSVC_OPTIMIZE_SMALL() use in x64_sequences, x64 backend is now always size optimized because of premake Make global_critical_region_ static constexpr in shared_memory.h to get rid of wasteage of 8 bytes (empty class=1byte, +alignment for next member=8) Move trace-related data to the tail of SharedMemory to keep more important data together In IssueDraw build an array of fetch constant addresses/sizes, then pre-lock the global lock before doing requestrange for each instead of individually locking within requestrange for each of them Consistent access specifier protected for pm4_command_processor_declare Devirtualize WriteOneRegisterFromRing. Move ExecutePacket and ExecutePrimaryBuffer to pm4_command_buffer_x Remove many redundant header inclusions access xenia-gpu Minor microoptimization of ExecutePacketType0 Add TextureCache::RequestTextures for batch invocation of LoadTexturesData Add TextureCache::LoadTexturesData for reducing the number of times we release and reacquire the global lock. Ideally you should hold the global lock for as little time as possible, but if you are constantly acquiring and releasing it you are actually more likely to have contention Add already_locked param to ObjectTable::LookupObject to help with reducing lock acquire/release pairs Add missing checks to XAudioRegisterRenderDriverClient_entry. this is unlikely to fix anything, it was just an easy thing to do Add NtQueryEvent system call implementation. I don't actually know of any games that need it. Instead of using std::vector + push_back in KeWaitForMultipleObjects and xeNtWaitForMultipleObjectsEx use a fixed size array of 64 and track the count. More than 64 objects is not permitted by the kernel. The repeated reallocations from push_back were appearing unusually high on the profiler, but were masked until now by waitformultipleobjects natural overhead Pre-lock the global lock before looking up each handle for xeNtWaitForMultipleObjectsEx and KeWaitForMultipleObjects. Pre-lock before looking up the signal and waiter in NtSignalAndWaitForSingleObjectEx add missing checks to NtWaitForMultipleObjectsEx Support pre-locking in XObject::GetNativeObject
148 lines
4.9 KiB
C++
148 lines
4.9 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2022 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#ifndef XENIA_GPU_SHADER_INTERPRETER_H_
|
|
#define XENIA_GPU_SHADER_INTERPRETER_H_
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
#include "xenia/gpu/register_file.h"
|
|
#include "xenia/gpu/shader.h"
|
|
#include "xenia/gpu/trace_writer.h"
|
|
#include "xenia/memory.h"
|
|
|
|
namespace xe {
|
|
namespace gpu {
|
|
|
|
class ShaderInterpreter {
|
|
public:
|
|
ShaderInterpreter(const RegisterFile& register_file, const Memory& memory)
|
|
: register_file_(register_file), memory_(memory) {}
|
|
|
|
class ExportSink {
|
|
public:
|
|
virtual ~ExportSink() = default;
|
|
virtual void AllocExport(ucode::AllocType type, uint32_t size) {}
|
|
virtual void Export(ucode::ExportRegister export_register,
|
|
const float* value, uint32_t value_mask) {}
|
|
};
|
|
|
|
void SetTraceWriter(TraceWriter* new_trace_writer) {
|
|
trace_writer_ = new_trace_writer;
|
|
}
|
|
|
|
ExportSink* GetExportSink() const { return export_sink_; }
|
|
void SetExportSink(ExportSink* new_export_sink) {
|
|
export_sink_ = new_export_sink;
|
|
}
|
|
|
|
const float* temp_registers() const { return &temp_registers_[0][0]; }
|
|
float* temp_registers() { return &temp_registers_[0][0]; }
|
|
|
|
static bool CanInterpretShader(const Shader& shader) {
|
|
assert_true(shader.is_ucode_analyzed());
|
|
// Texture instructions are not very common in vertex shaders (and not used
|
|
// in Direct3D 9's internal rectangles such as clears) and are extremely
|
|
// complex, not implemented.
|
|
if (shader.uses_texture_fetch_instruction_results()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
void SetShader(xenos::ShaderType shader_type, const uint32_t* ucode) {
|
|
shader_type_ = shader_type;
|
|
ucode_ = ucode;
|
|
}
|
|
void SetShader(const Shader& shader) {
|
|
assert_true(CanInterpretShader(shader));
|
|
SetShader(shader.type(), shader.ucode_dwords());
|
|
}
|
|
|
|
void Execute();
|
|
|
|
private:
|
|
struct State {
|
|
ucode::VertexFetchInstruction vfetch_full_last;
|
|
uint32_t vfetch_address_dwords;
|
|
float previous_scalar;
|
|
uint32_t call_stack_depth;
|
|
uint32_t call_return_addresses[4];
|
|
uint32_t loop_stack_depth;
|
|
xenos::LoopConstant loop_constants[4];
|
|
uint32_t loop_iterators[4];
|
|
int32_t address_register;
|
|
bool predicate;
|
|
|
|
void Reset() { std::memset(this, 0, sizeof(*this)); }
|
|
|
|
int32_t GetLoopAddress() const {
|
|
assert_true(loop_stack_depth && loop_stack_depth < 4);
|
|
if (!loop_stack_depth || loop_stack_depth >= 4) {
|
|
return 0;
|
|
}
|
|
xenos::LoopConstant loop_constant = loop_constants[loop_stack_depth];
|
|
// Clamp to the real range specified in the IPR2015-00325 sequencer
|
|
// specification.
|
|
// https://portal.unifiedpatents.com/ptab/case/IPR2015-00325
|
|
return std::min(
|
|
INT32_C(256),
|
|
std::max(INT32_C(-256),
|
|
int32_t(int32_t(loop_iterators[loop_stack_depth]) *
|
|
loop_constant.step +
|
|
loop_constant.start)));
|
|
}
|
|
};
|
|
|
|
static float FlushDenormal(float value) {
|
|
uint32_t bits = *reinterpret_cast<const uint32_t*>(&value);
|
|
bits &= (bits & UINT32_C(0x7F800000)) ? ~UINT32_C(0) : (UINT32_C(1) << 31);
|
|
return *reinterpret_cast<const float*>(&bits);
|
|
}
|
|
|
|
uint32_t GetTempRegisterIndex(uint32_t address, bool is_relative) const {
|
|
return (int32_t(address) + (is_relative ? state_.GetLoopAddress() : 0)) &
|
|
((UINT32_C(1) << xenos::kMaxShaderTempRegistersLog2) - 1);
|
|
}
|
|
const float* GetTempRegister(uint32_t address, bool is_relative) const {
|
|
return temp_registers_[GetTempRegisterIndex(address, is_relative)];
|
|
}
|
|
float* GetTempRegister(uint32_t address, bool is_relative) {
|
|
return temp_registers_[GetTempRegisterIndex(address, is_relative)];
|
|
}
|
|
const float* GetFloatConstant(uint32_t address, bool is_relative,
|
|
bool relative_address_is_a0) const;
|
|
|
|
void ExecuteAluInstruction(ucode::AluInstruction instr);
|
|
void StoreFetchResult(uint32_t dest, bool is_dest_relative, uint32_t swizzle,
|
|
const float* value);
|
|
void ExecuteVertexFetchInstruction(ucode::VertexFetchInstruction instr);
|
|
|
|
const RegisterFile& register_file_;
|
|
const Memory& memory_;
|
|
|
|
TraceWriter* trace_writer_ = nullptr;
|
|
|
|
ExportSink* export_sink_ = nullptr;
|
|
|
|
xenos::ShaderType shader_type_ = xenos::ShaderType::kVertex;
|
|
const uint32_t* ucode_ = nullptr;
|
|
|
|
// For both inputs and locals.
|
|
float temp_registers_[xenos::kMaxShaderTempRegisters][4];
|
|
|
|
State state_;
|
|
};
|
|
|
|
} // namespace gpu
|
|
} // namespace xe
|
|
|
|
#endif // XENIA_GPU_SHADER_INTERPRETER_H_
|