rpcs3/rpcs3/Emu/RSX/NV47/FW/draw_call.cpp
2025-04-19 01:04:00 +03:00

253 lines
6.7 KiB
C++

#include "stdafx.h"
#include "draw_call.hpp"
#include "Emu/RSX/rsx_methods.h" // FIXME
#include "Emu/RSX/rsx_utils.h"
#include "Emu/RSX/RSXThread.h"
#include "Emu/RSX/Common/BufferUtils.h"
#include "Emu/RSX/NV47/HW/context.h"
#include "Emu/RSX/NV47/HW/nv4097.h"
// Always import this after other HW definitions
#include "Emu/RSX/NV47/HW/context_accessors.define.h"
#include <util/serialization.hpp>
namespace rsx
{
void draw_clause::operator()(utils::serial& ar)
{
ar(draw_command_ranges, draw_command_barriers, current_range_index, primitive, command, is_immediate_draw, is_disjoint_primitive, primitive_barrier_enable, inline_vertex_array);
}
void draw_clause::insert_command_barrier(command_barrier_type type, u32 arg0, u32 arg1, u32 index)
{
ensure(!draw_command_ranges.empty());
auto _do_barrier_insert = [this](barrier_t&& val)
{
if (draw_command_barriers.empty() || draw_command_barriers.back() < val)
{
draw_command_barriers.push_back(val);
return;
}
for (auto it = draw_command_barriers.begin(); it != draw_command_barriers.end(); it++)
{
if (*it < val)
{
continue;
}
draw_command_barriers.insert(it, val);
break;
}
};
draw_command_barrier_mask |= (1u << type);
if (type == primitive_restart_barrier)
{
// Rasterization flow barrier
const auto& last = draw_command_ranges[current_range_index];
const auto address = last.first + last.count;
_do_barrier_insert({
.draw_id = current_range_index,
.timestamp = 0,
.address = address,
.index = index,
.arg0 = arg0,
.arg1 = arg1,
.flags = 0,
.type = type
});
}
else
{
// Execution dependency barrier. Requires breaking the current draw call sequence and start another.
if (draw_command_ranges.back().count > 0)
{
append_draw_command({});
}
else
{
// In case of back-to-back modifiers, do not add duplicates
current_range_index = draw_command_ranges.size() - 1;
}
_do_barrier_insert({
.draw_id = current_range_index,
.timestamp = rsx::get_shared_tag(),
.address = ~0u,
.index = index,
.arg0 = arg0,
.arg1 = arg1,
.flags = 0,
.type = type
});
last_execution_barrier_index = current_range_index;
}
}
bool draw_clause::check_trivially_instanced() const
{
if (pass_count() <= 1)
{
// Cannot instance one draw call or less
return false;
}
if (draw_command_barriers.empty())
{
return false;
}
// Barriers must exist, but can only involve updating transform constants (for now)
const u32 compatible_barrier_mask =
(1u << rsx::transform_constant_load_modifier_barrier) |
(1u << rsx::transform_constant_update_barrier);
if (draw_command_barrier_mask & ~compatible_barrier_mask)
{
return false;
}
// For instancing all draw calls must be identical
// FIXME: This requirement can be easily lifted by chunking contiguous chunks.
const auto& ref = draw_command_ranges.front();
return !draw_command_ranges.any(FN(x.first != ref.first || x.count != ref.count));
}
void draw_clause::reset(primitive_type type)
{
current_range_index = ~0u;
last_execution_barrier_index = 0;
draw_command_barrier_mask = 0;
command = draw_command::none;
primitive = type;
primitive_barrier_enable = false;
is_trivial_instanced_draw = false;
draw_command_ranges.clear();
draw_command_barriers.clear();
inline_vertex_array.clear();
is_disjoint_primitive = is_primitive_disjointed(primitive);
}
simple_array<draw_range_t> draw_clause::get_subranges() const
{
ensure(!is_single_draw());
const auto range = get_range();
const auto limit = range.first + range.count;
const auto _pass_count = pass_count();
simple_array<draw_range_t> ret;
ret.reserve(_pass_count);
u32 previous_barrier = range.first;
u32 vertex_counter = 0;
for (auto it = current_barrier_it;
it != draw_command_barriers.end() && it->draw_id == current_range_index;
it++)
{
const auto& barrier = *it;
if (barrier.type != primitive_restart_barrier)
continue;
if (barrier.address <= range.first)
continue;
if (barrier.address >= limit)
break;
const u32 count = barrier.address - previous_barrier;
ret.push_back({ 0, vertex_counter, count });
previous_barrier = barrier.address;
vertex_counter += count;
}
ensure(!ret.empty());
ensure(previous_barrier < limit);
ret.push_back({ 0, vertex_counter, limit - previous_barrier });
return ret;
}
u32 draw_clause::execute_pipeline_dependencies(context* ctx, instanced_draw_config_t* instance_config) const
{
u32 result = 0u;
for (;
current_barrier_it != draw_command_barriers.end() && current_barrier_it->draw_id == current_range_index;
current_barrier_it++)
{
const auto& barrier = *current_barrier_it;
switch (barrier.type)
{
case primitive_restart_barrier:
{
break;
}
case index_base_modifier_barrier:
{
// Change index base offset
REGS(ctx)->decode(NV4097_SET_VERTEX_DATA_BASE_INDEX, barrier.arg0);
result |= index_base_changed;
break;
}
case vertex_base_modifier_barrier:
{
// Change vertex base offset
REGS(ctx)->decode(NV4097_SET_VERTEX_DATA_BASE_OFFSET, barrier.arg0);
result |= vertex_base_changed;
break;
}
case vertex_array_offset_modifier_barrier:
{
// Change vertex array offset
REGS(ctx)->decode(NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + barrier.index, barrier.arg0);
result |= vertex_arrays_changed;
break;
}
case transform_constant_load_modifier_barrier:
{
// Change the transform load target. Does not change result mask.
REGS(ctx)->decode(NV4097_SET_TRANSFORM_PROGRAM_LOAD, barrier.arg0);
break;
}
case transform_constant_update_barrier:
{
// Update transform constants
auto ptr = RSX(ctx)->fifo_ctrl->translate_address(barrier.arg0);
auto buffer = std::span<const u32>(static_cast<const u32*>(vm::base(ptr)), barrier.arg1);
auto notify = [&](rsx::context*, u32 load, u32 count)
{
if (!instance_config)
{
return false;
}
instance_config->transform_constants_data_changed = true;
instance_config->patch_load_offset = load;
instance_config->patch_load_count = count;
return true;
};
nv4097::set_transform_constant::batch_decode(ctx, NV4097_SET_TRANSFORM_CONSTANT + barrier.index, buffer, notify);
result |= transform_constants_changed;
break;
}
default:
fmt::throw_exception("Unreachable");
}
}
return result;
}
}