mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-10-12 02:09:17 +00:00
rsx: Rework transform constant update logic
- This reverts commit 193975556d.
- Out of bounds writes are treated as NOP, same as real hardware
This commit is contained in:
@@ -21,73 +21,113 @@ namespace rsx
|
||||
RSX(ctx)->m_graphics_state |= rsx::pipeline_state::fragment_program_ucode_dirty;
|
||||
}
|
||||
|
||||
void set_transform_constant::decode_one([[maybe_unused]] context* ctx, u32 reg, u32 arg)
|
||||
set_transform_constant::write_range set_transform_constant::compute_write_range(context* ctx, u32 reg, u32 count)
|
||||
{
|
||||
const u32 index = reg - NV4097_SET_TRANSFORM_CONSTANT;
|
||||
const u32 constant_id = index / 4;
|
||||
const u8 subreg = index % 4;
|
||||
const u32 load = REGS(ctx)->transform_constant_load();
|
||||
if (load >= max_transform_constants)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
REGS(ctx)->transform_constants[load + constant_id][subreg] = arg;
|
||||
const u32 first_word = load * 4 + (reg - NV4097_SET_TRANSFORM_CONSTANT);
|
||||
const u32 max_words = max_transform_constants * 4;
|
||||
if (first_word >= max_words)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return { first_word, std::min(count, max_words - first_word) };
|
||||
}
|
||||
|
||||
u32* set_transform_constant::get_constants_ptr(context* ctx, u32 word)
|
||||
{
|
||||
return ®S(ctx)->transform_constants[word / 4][word % 4];
|
||||
}
|
||||
|
||||
void set_transform_constant::decode_one(context* ctx, u32 reg, u32 arg)
|
||||
{
|
||||
const auto range = compute_write_range(ctx, reg, 1);
|
||||
if (!range.word_count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
*get_constants_ptr(ctx, range.first_word) = arg;
|
||||
}
|
||||
|
||||
void set_transform_constant::batch_decode(context* ctx, u32 reg, const std::span<const u32>& args, const std::function<bool(context*, u32, u32)>& notify)
|
||||
{
|
||||
const u32 index = reg - NV4097_SET_TRANSFORM_CONSTANT;
|
||||
const u32 constant_id = index / 4;
|
||||
const u8 subreg = index % 4;
|
||||
const u32 load = REGS(ctx)->transform_constant_load();
|
||||
|
||||
auto dst = ®S(ctx)->transform_constants[load + constant_id][subreg];
|
||||
copy_data_swap_u32(dst, args.data(), ::size32(args));
|
||||
|
||||
// Notify
|
||||
const u32 last_constant_id = ((reg + ::size32(args) + 3) - NV4097_SET_TRANSFORM_CONSTANT) / 4; // Aligned div
|
||||
const u32 load_index = load + constant_id;
|
||||
const u32 load_count = last_constant_id - constant_id;
|
||||
|
||||
if (!notify || !notify(ctx, load_index, load_count))
|
||||
const auto range = compute_write_range(ctx, reg, ::size32(args));
|
||||
if (!range.word_count)
|
||||
{
|
||||
RSX(ctx)->patch_transform_constants(ctx, load_index, load_count);
|
||||
return;
|
||||
}
|
||||
|
||||
copy_data_swap_u32(get_constants_ptr(ctx, range.first_word), args.data(), range.word_count);
|
||||
|
||||
// Notify using the range of vec4 constants touched by the write
|
||||
const u32 first_constant = range.first_word / 4;
|
||||
const u32 end_constant = (range.first_word + range.word_count + 3) / 4;
|
||||
const u32 constant_count = end_constant - first_constant;
|
||||
|
||||
if (!notify || !notify(ctx, first_constant, constant_count))
|
||||
{
|
||||
RSX(ctx)->patch_transform_constants(ctx, first_constant, constant_count);
|
||||
}
|
||||
}
|
||||
|
||||
void set_transform_constant::write_constants(context* ctx, u32 first_word, const u32* src, u32 count)
|
||||
{
|
||||
const auto dst = get_constants_ptr(ctx, first_word);
|
||||
|
||||
if (RSX(ctx)->m_graphics_state & rsx::pipeline_state::transform_constants_dirty)
|
||||
{
|
||||
// Minor optimization: don't compare values if we already know we need invalidation
|
||||
copy_data_swap_u32(dst, src, count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (copy_data_swap_u32_cmp(dst, src, count))
|
||||
{
|
||||
// Transform constants invalidation is expensive (~8k bytes per update)
|
||||
RSX(ctx)->m_graphics_state |= rsx::pipeline_state::transform_constants_dirty;
|
||||
}
|
||||
}
|
||||
|
||||
void set_transform_constant::impl(context* ctx, u32 reg, [[maybe_unused]] u32 arg)
|
||||
{
|
||||
const u32 index = reg - NV4097_SET_TRANSFORM_CONSTANT;
|
||||
const u32 constant_id = index / 4;
|
||||
const u8 subreg = index % 4;
|
||||
const bool non_increment = (RSX(ctx)->fifo_ctrl->last_cmd() & RSX_METHOD_NON_INCREMENT_CMD_MASK) == RSX_METHOD_NON_INCREMENT_CMD;
|
||||
|
||||
// FIFO args count including this one
|
||||
const u32 fifo_args_cnt = RSX(ctx)->fifo_ctrl->get_remaining_args_count() + 1;
|
||||
|
||||
// The range of methods this function resposible to
|
||||
const u32 method_range = 32 - index;
|
||||
// The range of methods this function resposible to.
|
||||
// Incrementing commands advance the register per arg, so args beyond the 32-register window target other methods.
|
||||
// Non-incrementing commands send every arg to this same register.
|
||||
const u32 method_range = non_increment ? fifo_args_cnt : 32 - index;
|
||||
|
||||
// Get limit imposed by FIFO PUT (if put is behind get it will result in a number ignored by min)
|
||||
const u32 fifo_read_limit = static_cast<u32>(((RSX(ctx)->ctrl->put & ~3ull) - (RSX(ctx)->fifo_ctrl->get_pos())) / 4);
|
||||
|
||||
// Number of args owned by this method. Every one of these must be consumed, even if the write is trimmed or dropped entirely.
|
||||
const u32 count = std::min<u32>({ fifo_args_cnt, fifo_read_limit, method_range });
|
||||
|
||||
const u32 load = REGS(ctx)->transform_constant_load();
|
||||
// Non-incrementing writes all land on the same word, so only the last arg sticks
|
||||
const u32 src_offset = non_increment ? count - 1 : 0;
|
||||
const u32 write_count = count - src_offset;
|
||||
|
||||
u32 rcount = count;
|
||||
if (const u32 max = (load + constant_id) * 4 + count + subreg, limit = 468 * 4; max > limit)
|
||||
const auto range = compute_write_range(ctx, reg, write_count);
|
||||
if (range.word_count < write_count)
|
||||
{
|
||||
// Ignore addresses outside the usable [0, 467] range
|
||||
rsx_log.warning("Invalid transform register index (load=%u, index=%u, count=%u)", load, index, count);
|
||||
|
||||
if ((max - count) < limit)
|
||||
rcount -= max - limit;
|
||||
else
|
||||
rcount = 0;
|
||||
rsx_log.warning("Invalid transform register index (load=%u, index=%u, count=%u)", REGS(ctx)->transform_constant_load(), index, write_count);
|
||||
}
|
||||
|
||||
if (rcount == 0)
|
||||
if (range.word_count == 0)
|
||||
{
|
||||
// Out-of-bounds write is a NOP
|
||||
rsx_log.trace("Out of bounds write for transform constant block.");
|
||||
RSX(ctx)->fifo_ctrl->skip_methods(fifo_args_cnt - 1);
|
||||
RSX(ctx)->fifo_ctrl->skip_methods(count - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,39 +136,36 @@ namespace rsx
|
||||
// Updating constants mid-draw is messy. Defer the writes
|
||||
REGS(ctx)->current_draw_clause.insert_command_barrier(
|
||||
rsx::transform_constant_update_barrier,
|
||||
RSX(ctx)->fifo_ctrl->get_pos(),
|
||||
rcount,
|
||||
reg - NV4097_SET_TRANSFORM_CONSTANT
|
||||
RSX(ctx)->fifo_ctrl->get_pos() + src_offset * 4,
|
||||
range.word_count,
|
||||
index
|
||||
);
|
||||
|
||||
RSX(ctx)->fifo_ctrl->skip_methods(rcount - 1);
|
||||
RSX(ctx)->fifo_ctrl->skip_methods(count - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto values = ®S(ctx)->transform_constants[load + constant_id][subreg];
|
||||
const u32 read_count = src_offset + range.word_count;
|
||||
const auto fifo_span = RSX(ctx)->fifo_ctrl->get_current_arg_ptr(read_count);
|
||||
|
||||
const auto fifo_span = RSX(ctx)->fifo_ctrl->get_current_arg_ptr(rcount);
|
||||
|
||||
if (fifo_span.size() < rcount)
|
||||
if (const u32 available = ::size32(fifo_span); available < read_count)
|
||||
{
|
||||
rcount = ::size32(fifo_span);
|
||||
}
|
||||
|
||||
if (RSX(ctx)->m_graphics_state & rsx::pipeline_state::transform_constants_dirty)
|
||||
{
|
||||
// Minor optimization: don't compare values if we already know we need invalidation
|
||||
copy_data_swap_u32(values, fifo_span.data(), rcount);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (copy_data_swap_u32_cmp(values, fifo_span.data(), rcount))
|
||||
// FIFO data is not contiguous. Consume what we can see, the remaining args are dispatched again.
|
||||
if (non_increment)
|
||||
{
|
||||
// Transform constants invalidation is expensive (~8k bytes per update)
|
||||
RSX(ctx)->m_graphics_state |= rsx::pipeline_state::transform_constants_dirty;
|
||||
write_constants(ctx, range.first_word, &fifo_span[available - 1], 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_constants(ctx, range.first_word, fifo_span.data(), available);
|
||||
}
|
||||
|
||||
RSX(ctx)->fifo_ctrl->skip_methods(available - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
RSX(ctx)->fifo_ctrl->skip_methods(rcount - 1);
|
||||
write_constants(ctx, range.first_word, fifo_span.data() + src_offset, range.word_count);
|
||||
RSX(ctx)->fifo_ctrl->skip_methods(count - 1);
|
||||
}
|
||||
|
||||
void set_transform_program::impl(context* ctx, u32 reg, u32 /*arg*/)
|
||||
@@ -144,35 +181,32 @@ namespace rsx
|
||||
// Get limit imposed by FIFO PUT (if put is behind get it will result in a number ignored by min)
|
||||
const u32 fifo_read_limit = static_cast<u32>(((RSX(ctx)->ctrl->put & ~3ull) - (RSX(ctx)->fifo_ctrl->get_pos())) / 4);
|
||||
|
||||
// Number of args owned by this method. Every one of these must be consumed, even if the write is trimmed or dropped entirely.
|
||||
const u32 count = std::min<u32>({ fifo_args_cnt, fifo_read_limit, method_range });
|
||||
|
||||
// Writes start at the current load position, which advances by one instruction per 4 words written
|
||||
const u32 load_pos = REGS(ctx)->transform_program_load();
|
||||
const u32 max_words = max_vertex_program_instructions * 4;
|
||||
const u32 first_word = load_pos < max_vertex_program_instructions ? load_pos * 4 + index % 4 : max_words;
|
||||
const u32 write_count = std::min(count, max_words - first_word);
|
||||
|
||||
u32 rcount = count;
|
||||
|
||||
if (const u32 max = load_pos * 4 + rcount + (index % 4);
|
||||
max > max_vertex_program_instructions * 4)
|
||||
if (write_count < count)
|
||||
{
|
||||
rsx_log.warning("Program buffer overflow! Attempted to write %u VP instructions.", max / 4);
|
||||
rcount -= max - (max_vertex_program_instructions * 4);
|
||||
rsx_log.warning("Program buffer overflow! (load=%u, index=%u, count=%u)", load_pos, index, count);
|
||||
}
|
||||
|
||||
if (!rcount)
|
||||
if (write_count == 0)
|
||||
{
|
||||
// Out-of-bounds write is a NOP
|
||||
rsx_log.trace("Out of bounds write for transform program block.");
|
||||
RSX(ctx)->fifo_ctrl->skip_methods(fifo_args_cnt - 1);
|
||||
RSX(ctx)->fifo_ctrl->skip_methods(count - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto fifo_span = RSX(ctx)->fifo_ctrl->get_current_arg_ptr(rcount);
|
||||
const auto fifo_span = RSX(ctx)->fifo_ctrl->get_current_arg_ptr(write_count);
|
||||
const u32 rcount = std::min(write_count, ::size32(fifo_span));
|
||||
|
||||
if (fifo_span.size() < rcount)
|
||||
{
|
||||
rcount = ::size32(fifo_span);
|
||||
}
|
||||
|
||||
const auto out_ptr = ®S(ctx)->transform_program[load_pos * 4 + index % 4];
|
||||
const auto out_ptr = ®S(ctx)->transform_program[first_word];
|
||||
|
||||
pipeline_state to_set_dirty = rsx::pipeline_state::vertex_program_ucode_dirty;
|
||||
|
||||
@@ -205,7 +239,9 @@ namespace rsx
|
||||
|
||||
RSX(ctx)->m_graphics_state |= to_set_dirty;
|
||||
REGS(ctx)->transform_program_load_set(load_pos + ((rcount + index % 4) / 4));
|
||||
RSX(ctx)->fifo_ctrl->skip_methods(rcount - 1);
|
||||
|
||||
// If the FIFO span came up short, the remaining in-range args are dispatched again. Otherwise consume everything, including the trimmed tail.
|
||||
RSX(ctx)->fifo_ctrl->skip_methods((rcount < write_count ? rcount : count) - 1);
|
||||
}
|
||||
|
||||
///// Texture management
|
||||
|
||||
@@ -204,6 +204,22 @@ namespace rsx
|
||||
|
||||
struct set_transform_constant
|
||||
{
|
||||
// Helper for write commands
|
||||
struct write_range
|
||||
{
|
||||
u32 first_word = 0;
|
||||
u32 word_count = 0;
|
||||
};
|
||||
|
||||
// Only constants [0, 467] are writable. Hardware ignores writes beyond that.
|
||||
static constexpr u32 max_transform_constants = 468u;
|
||||
|
||||
static u32* get_constants_ptr(context* ctx, u32 word);
|
||||
|
||||
static write_range compute_write_range(context* ctx, u32 reg, u32 count);
|
||||
|
||||
static void write_constants(context* ctx, u32 first_word, const u32* src, u32 count);
|
||||
|
||||
static void impl(context* ctx, u32 reg, u32 arg);
|
||||
|
||||
static void decode_one(context* ctx, u32 reg, u32 arg);
|
||||
|
||||
Reference in New Issue
Block a user