diff options
Diffstat (limited to 'Source/Core/VideoCommon/VertexManagerBase.cpp')
| -rw-r--r-- | Source/Core/VideoCommon/VertexManagerBase.cpp | 329 |
1 files changed, 223 insertions, 106 deletions
diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index 0e178e4d9b..bbc355a98c 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -17,8 +17,9 @@ #include "Core/ConfigManager.h" #include "VideoCommon/BPMemory.h" +#include "VideoCommon/BoundingBox.h" #include "VideoCommon/DataReader.h" -#include "VideoCommon/Debugger.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/GeometryShaderManager.h" #include "VideoCommon/IndexGenerator.h" #include "VideoCommon/NativeVertexFormat.h" @@ -79,11 +80,15 @@ static bool AspectIs16_9(float width, float height) } VertexManagerBase::VertexManagerBase() + : m_cpu_vertex_buffer(MAXVBUFFERSIZE), m_cpu_index_buffer(MAXIBUFFERSIZE) { } -VertexManagerBase::~VertexManagerBase() +VertexManagerBase::~VertexManagerBase() = default; + +bool VertexManagerBase::Initialize() { + return true; } u32 VertexManagerBase::GetRemainingSize() const @@ -94,6 +99,10 @@ u32 VertexManagerBase::GetRemainingSize() const DataReader VertexManagerBase::PrepareForAdditionalData(int primitive, u32 count, u32 stride, bool cullall) { + // Flush all EFB pokes and invalidate the peek cache. + g_framebuffer_manager->InvalidatePeekCache(); + g_framebuffer_manager->FlushEFBPokes(); + // The SSE vertex loader can write up to 4 bytes past the end u32 const needed_vertex_bytes = count * stride + 4; @@ -132,7 +141,18 @@ DataReader VertexManagerBase::PrepareForAdditionalData(int primitive, u32 count, // need to alloc new buffer if (m_is_flushed) { - g_vertex_manager->ResetBuffer(stride, cullall); + if (cullall) + { + // This buffer isn't getting sent to the GPU. Just allocate it on the cpu. + m_cur_buffer_pointer = m_base_buffer_pointer = m_cpu_vertex_buffer.data(); + m_end_buffer_pointer = m_base_buffer_pointer + m_cpu_vertex_buffer.size(); + IndexGenerator::Start(m_cpu_index_buffer.data()); + } + else + { + ResetBuffer(stride); + } + m_is_flushed = false; } @@ -210,6 +230,48 @@ std::pair<size_t, size_t> VertexManagerBase::ResetFlushAspectRatioCount() return val; } +void VertexManagerBase::ResetBuffer(u32 vertex_stride) +{ + m_base_buffer_pointer = m_cpu_vertex_buffer.data(); + m_cur_buffer_pointer = m_cpu_vertex_buffer.data(); + m_end_buffer_pointer = m_base_buffer_pointer + m_cpu_vertex_buffer.size(); + IndexGenerator::Start(m_cpu_index_buffer.data()); +} + +void VertexManagerBase::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, + u32* out_base_vertex, u32* out_base_index) +{ + *out_base_vertex = 0; + *out_base_index = 0; +} + +void VertexManagerBase::DrawCurrentBatch(u32 base_index, u32 num_indices, u32 base_vertex) +{ + // If bounding box is enabled, we need to flush any changes first, then invalidate what we have. + if (::BoundingBox::active && g_ActiveConfig.bBBoxEnable && + g_ActiveConfig.backend_info.bSupportsBBox) + { + g_renderer->BBoxFlush(); + } + + g_renderer->DrawIndexed(base_index, num_indices, base_vertex); +} + +void VertexManagerBase::UploadUniforms() +{ +} + +void VertexManagerBase::InvalidateConstants() +{ + VertexShaderManager::dirty = true; + GeometryShaderManager::dirty = true; + PixelShaderManager::dirty = true; +} + +void VertexManagerBase::UploadUtilityUniforms(const void* uniforms, u32 uniforms_size) +{ +} + void VertexManagerBase::UploadUtilityVertices(const void* vertices, u32 vertex_stride, u32 num_vertices, const u16* indices, u32 num_indices, u32* out_base_vertex, u32* out_base_index) @@ -218,7 +280,7 @@ void VertexManagerBase::UploadUtilityVertices(const void* vertices, u32 vertex_s ASSERT(m_is_flushed); // Copy into the buffers usually used for GX drawing. - ResetBuffer(std::max(vertex_stride, 1u), false); + ResetBuffer(std::max(vertex_stride, 1u)); if (vertices) { const u32 copy_size = vertex_stride * num_vertices; @@ -232,64 +294,42 @@ void VertexManagerBase::UploadUtilityVertices(const void* vertices, u32 vertex_s CommitBuffer(num_vertices, vertex_stride, num_indices, out_base_vertex, out_base_index); } -static void SetSamplerState(u32 index, float custom_tex_scale, bool custom_tex, - bool has_arbitrary_mips) +u32 VertexManagerBase::GetTexelBufferElementSize(TexelBufferFormat buffer_format) { - const FourTexUnits& tex = bpmem.tex[index / 4]; - const TexMode0& tm0 = tex.texMode0[index % 4]; - - SamplerState state = {}; - state.Generate(bpmem, index); - - // Force texture filtering config option. - if (g_ActiveConfig.bForceFiltering) - { - state.min_filter = SamplerState::Filter::Linear; - state.mag_filter = SamplerState::Filter::Linear; - state.mipmap_filter = SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0) ? - SamplerState::Filter::Linear : - SamplerState::Filter::Point; - } - - // Custom textures may have a greater number of mips - if (custom_tex) - state.max_lod = 255; - - // Anisotropic filtering option. - if (g_ActiveConfig.iMaxAnisotropy != 0 && !SamplerCommon::IsBpTexMode0PointFiltering(tm0)) - { - // https://www.opengl.org/registry/specs/EXT/texture_filter_anisotropic.txt - // For predictable results on all hardware/drivers, only use one of: - // GL_LINEAR + GL_LINEAR (No Mipmaps [Bilinear]) - // GL_LINEAR + GL_LINEAR_MIPMAP_LINEAR (w/ Mipmaps [Trilinear]) - // Letting the game set other combinations will have varying arbitrary results; - // possibly being interpreted as equal to bilinear/trilinear, implicitly - // disabling anisotropy, or changing the anisotropic algorithm employed. - state.min_filter = SamplerState::Filter::Linear; - state.mag_filter = SamplerState::Filter::Linear; - if (SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0)) - state.mipmap_filter = SamplerState::Filter::Linear; - state.anisotropic_filtering = 1; - } - else - { - state.anisotropic_filtering = 0; - } + // R8 - 1, R16 - 2, RGBA8 - 4, R32G32 - 8 + return 1u << static_cast<u32>(buffer_format); +} - if (has_arbitrary_mips && SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0)) - { - // Apply a secondary bias calculated from the IR scale to pull inwards mipmaps - // that have arbitrary contents, eg. are used for fog effects where the - // distance they kick in at is important to preserve at any resolution. - // Correct this with the upscaling factor of custom textures. - s64 lod_offset = std::log2(g_renderer->GetEFBScale() / custom_tex_scale) * 256.f; - state.lod_bias = MathUtil::Clamp<s64>(state.lod_bias + lod_offset, -32768, 32767); +bool VertexManagerBase::UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, + u32* out_offset) +{ + return false; +} - // Anisotropic also pushes mips farther away so it cannot be used either - state.anisotropic_filtering = 0; - } +bool VertexManagerBase::UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, + u32* out_offset, const void* palette_data, + u32 palette_size, TexelBufferFormat palette_format, + u32* palette_offset) +{ + return false; +} + +void VertexManagerBase::LoadTextures() +{ + BitSet32 usedtextures; + for (u32 i = 0; i < bpmem.genMode.numtevstages + 1u; ++i) + if (bpmem.tevorders[i / 2].getEnable(i & 1)) + usedtextures[bpmem.tevorders[i / 2].getTexMap(i & 1)] = true; + + if (bpmem.genMode.numindstages > 0) + for (unsigned int i = 0; i < bpmem.genMode.numtevstages + 1u; ++i) + if (bpmem.tevind[i].IsActive() && bpmem.tevind[i].bt < bpmem.genMode.numindstages) + usedtextures[bpmem.tevindref.getTexMap(bpmem.tevind[i].bt)] = true; - g_renderer->SetSamplerState(index, state); + for (unsigned int i : usedtextures) + g_texture_cache->Load(i); + + g_texture_cache->BindTextures(); } void VertexManagerBase::Flush() @@ -297,6 +337,8 @@ void VertexManagerBase::Flush() if (m_is_flushed) return; + m_is_flushed = true; + // loading a state will invalidate BP, so check for it g_video_backend->CheckInvalidState(); @@ -340,41 +382,6 @@ void VertexManagerBase::Flush() (bpmem.alpha_test.hex >> 16) & 0xff); #endif - // If the primitave is marked CullAll. All we need to do is update the vertex constants and - // calculate the zfreeze refrence slope - if (!m_cull_all) - { - BitSet32 usedtextures; - for (u32 i = 0; i < bpmem.genMode.numtevstages + 1u; ++i) - if (bpmem.tevorders[i / 2].getEnable(i & 1)) - usedtextures[bpmem.tevorders[i / 2].getTexMap(i & 1)] = true; - - if (bpmem.genMode.numindstages > 0) - for (unsigned int i = 0; i < bpmem.genMode.numtevstages + 1u; ++i) - if (bpmem.tevind[i].IsActive() && bpmem.tevind[i].bt < bpmem.genMode.numindstages) - usedtextures[bpmem.tevindref.getTexMap(bpmem.tevind[i].bt)] = true; - - for (unsigned int i : usedtextures) - { - const auto* tentry = g_texture_cache->Load(i); - - if (tentry) - { - float custom_tex_scale = tentry->GetWidth() / float(tentry->native_width); - SetSamplerState(i, custom_tex_scale, tentry->is_custom_tex, tentry->has_arbitrary_mips); - PixelShaderManager::SetTexDims(i, tentry->native_width, tentry->native_height); - } - else - { - ERROR_LOG(VIDEO, "error loading texture"); - } - } - g_texture_cache->BindTextures(); - } - - // set global vertex constants - VertexShaderManager::SetConstants(); - // Track some stats used elsewhere by the anamorphic widescreen heuristic. if (!SConfig::GetInstance().bWii) { @@ -394,6 +401,7 @@ void VertexManagerBase::Flush() } // Calculate ZSlope for zfreeze + VertexShaderManager::SetConstants(); if (!bpmem.genMode.zfreeze) { // Must be done after VertexShaderManager::SetConstants() @@ -407,20 +415,24 @@ void VertexManagerBase::Flush() if (!m_cull_all) { - // Update and upload constants. Note for the Vulkan backend, this must occur before the - // vertex/index buffer is committed, otherwise the data will be associated with the - // previous command buffer, instead of the one with the draw if there is an overflow. - GeometryShaderManager::SetConstants(); - PixelShaderManager::SetConstants(); - UploadConstants(); - - // Now the vertices can be flushed to the GPU. + // Now the vertices can be flushed to the GPU. Everything following the CommitBuffer() call + // must be careful to not upload any utility vertices, as the binding will be lost otherwise. const u32 num_indices = IndexGenerator::GetIndexLen(); u32 base_vertex, base_index; CommitBuffer(IndexGenerator::GetNumVerts(), VertexLoaderManager::GetCurrentVertexFormat()->GetVertexStride(), num_indices, &base_vertex, &base_index); + // Texture loading can cause palettes to be applied (-> uniforms -> draws). + // Palette application does not use vertices, only a full-screen quad, so this is okay. + // Same with GPU texture decoding, which uses compute shaders. + LoadTextures(); + + // Now we can upload uniforms, as nothing else will override them. + GeometryShaderManager::SetConstants(); + PixelShaderManager::SetConstants(); + UploadUniforms(); + // Update the pipeline, or compile one if needed. UpdatePipelineConfig(); UpdatePipelineObject(); @@ -435,18 +447,17 @@ void VertexManagerBase::Flush() if (PerfQueryBase::ShouldEmulate()) g_perf_query->DisableQuery(bpmem.zcontrol.early_ztest ? PQG_ZCOMP_ZCOMPLOC : PQG_ZCOMP); + + OnDraw(); } } - GFX_DEBUGGER_PAUSE_AT(NEXT_FLUSH, true); - if (xfmem.numTexGen.numTexGens != bpmem.genMode.numtexgens) + { ERROR_LOG(VIDEO, "xf.numtexgens (%d) does not match bp.numtexgens (%d). Error in command stream.", xfmem.numTexGen.numTexGens, bpmem.genMode.numtexgens.Value()); - - m_is_flushed = true; - m_cull_all = false; + } } void VertexManagerBase::DoState(PointerWrap& p) @@ -649,3 +660,109 @@ void VertexManagerBase::UpdatePipelineObject() break; } } + +void VertexManagerBase::OnDraw() +{ + m_draw_counter++; + + // If we didn't have any CPU access last frame, do nothing. + if (m_scheduled_command_buffer_kicks.empty() || !m_allow_background_execution) + return; + + // Check if this draw is scheduled to kick a command buffer. + // The draw counters will always be sorted so a binary search is possible here. + if (std::binary_search(m_scheduled_command_buffer_kicks.begin(), + m_scheduled_command_buffer_kicks.end(), m_draw_counter)) + { + // Kick a command buffer on the background thread. + g_renderer->Flush(); + } +} + +void VertexManagerBase::OnCPUEFBAccess() +{ + // Check this isn't another access without any draws inbetween. + if (!m_cpu_accesses_this_frame.empty() && m_cpu_accesses_this_frame.back() == m_draw_counter) + return; + + // Store the current draw counter for scheduling in OnEndFrame. + m_cpu_accesses_this_frame.emplace_back(m_draw_counter); +} + +void VertexManagerBase::OnEFBCopyToRAM() +{ + // If we're not deferring, try to preempt it next frame. + if (!g_ActiveConfig.bDeferEFBCopies) + { + OnCPUEFBAccess(); + return; + } + + // Otherwise, only execute if we have at least 10 objects between us and the last copy. + const u32 diff = m_draw_counter - m_last_efb_copy_draw_counter; + m_last_efb_copy_draw_counter = m_draw_counter; + if (diff < MINIMUM_DRAW_CALLS_PER_COMMAND_BUFFER_FOR_READBACK) + return; + + g_renderer->Flush(); +} + +void VertexManagerBase::OnEndFrame() +{ + m_draw_counter = 0; + m_last_efb_copy_draw_counter = 0; + m_scheduled_command_buffer_kicks.clear(); + + // If we have no CPU access at all, leave everything in the one command buffer for maximum + // parallelism between CPU/GPU, at the cost of slightly higher latency. + if (m_cpu_accesses_this_frame.empty()) + return; + + // In order to reduce CPU readback latency, we want to kick a command buffer roughly halfway + // between the draw counters that invoked the readback, or every 250 draws, whichever is smaller. + if (g_ActiveConfig.iCommandBufferExecuteInterval > 0) + { + u32 last_draw_counter = 0; + u32 interval = static_cast<u32>(g_ActiveConfig.iCommandBufferExecuteInterval); + for (u32 draw_counter : m_cpu_accesses_this_frame) + { + // We don't want to waste executing command buffers for only a few draws, so set a minimum. + // Leave last_draw_counter as-is, so we get the correct number of draws between submissions. + u32 draw_count = draw_counter - last_draw_counter; + if (draw_count < MINIMUM_DRAW_CALLS_PER_COMMAND_BUFFER_FOR_READBACK) + continue; + + if (draw_count <= interval) + { + u32 mid_point = draw_count / 2; + m_scheduled_command_buffer_kicks.emplace_back(last_draw_counter + mid_point); + } + else + { + u32 counter = interval; + while (counter < draw_count) + { + m_scheduled_command_buffer_kicks.emplace_back(last_draw_counter + counter); + counter += interval; + } + } + + last_draw_counter = draw_counter; + } + } + +#if 0 + { + std::stringstream ss; + std::for_each(m_cpu_accesses_this_frame.begin(), m_cpu_accesses_this_frame.end(), [&ss](u32 idx) { ss << idx << ","; }); + WARN_LOG(VIDEO, "CPU EFB accesses in last frame: %s", ss.str().c_str()); + } + { + std::stringstream ss; + std::for_each(m_scheduled_command_buffer_kicks.begin(), m_scheduled_command_buffer_kicks.end(), [&ss](u32 idx) { ss << idx << ","; }); + WARN_LOG(VIDEO, "Scheduled command buffer kicks: %s", ss.str().c_str()); + } +#endif + + m_cpu_accesses_this_frame.clear(); +} |
