mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
Merge branch 'p5f/fm' of /mnt/c/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/.git/modules/MobileGL into codex/p5f-wave2
# Conflicts: # MobileGL/MG_Pipe/PipeApply.h # MobileGL/MG_Remote/Server/PipeApplier.cpp
This commit is contained in:
@@ -1391,7 +1391,7 @@ jobs:
|
||||
tail -5 "${RUNNER_TEMP}/dualblock-split.txt"
|
||||
echo "dual-block census run: rc=${rc}"
|
||||
|
||||
rows=$(grep -cvE '^\s*(#|$)' "${expected}")
|
||||
rows=$(awk '!/^[[:space:]]*(#|$)/ { n++ } END { print n+0 }' "${expected}")
|
||||
if [ "${rc}" -eq 0 ] && [ "${rows}" -gt 0 ]; then
|
||||
echo "::error::the dual-block census lane went GREEN with ${rows} expected pair(s) outstanding - the knob stopped arming, or the fields retired and dualblock-expected-fatals.txt was not emptied in the same commit"
|
||||
exit 1
|
||||
@@ -1407,42 +1407,17 @@ jobs:
|
||||
|
||||
# ---- P5e (gl), CONTRACT-P5E §7: MAGMA'S OWN LANE, EXPECTED RED -----------------------
|
||||
#
|
||||
# The two DirectVulkan.Split.NamedBlit entries abort on GetFramebufferBindingSlot@Clear
|
||||
# (VulkanRenderer.cpp:7648). That field retires in P5e ON ESPRYT and in P7 ON MAGMA, and
|
||||
# FieldOwnership.def carries ONE retiring-phase string for both - so admitting the pair to
|
||||
# keep them in the Espryt lane would forgive an Espryt Clear reading the frontend's binding
|
||||
# slot again, which is exactly the regression fb's handle arm exists to prevent. Splitting
|
||||
# the lane costs two entries; admitting the row would cost the gate.
|
||||
#
|
||||
# THE LABEL IS `integration-magma-split` AND THE SPELLING MATTERS: `ctest -L` is a REGEX, so
|
||||
# a label named `integration-split-magma` is still matched by `-L integration-split` and the
|
||||
# entries would never have left. Measured before the rename.
|
||||
#
|
||||
# IT IS ASSERTED RED, WITH ITS MARKER. "Magma is red" must not be allowed to decay into
|
||||
# "Magma did not run": the step fails both when the run goes green (P7 landed early, or the
|
||||
# entries stopped drawing) and when it goes red without the named abort in a private log.
|
||||
- name: Integration-magma-split under strict - expected red until P7 (CONTRACT-P5E section 7)
|
||||
# P5f fm: Magma's framebuffer, copy and mipmap verbs consume server records.
|
||||
# The label remains separate so both backends' coverage stays explicit.
|
||||
- name: Integration-magma-split under strict and dual-block state (P5f fm)
|
||||
working-directory: build-split
|
||||
env:
|
||||
MOBILEGL_ITEST_REQUIRE_GPU: "1"
|
||||
MOBILEGL_IPC_STRICT_ERRORS: "1"
|
||||
MOBILEGL_IPC_ROLE_SPLIT_STATE: "1"
|
||||
MOBILEGL_TRANSPORT: "inproc"
|
||||
run: |
|
||||
ulimit -c unlimited
|
||||
rc=0
|
||||
ctest -L integration-magma-split --no-tests=error -j 2 > "${RUNNER_TEMP}/strict-magma.txt" 2>&1 || rc=$?
|
||||
tail -5 "${RUNNER_TEMP}/strict-magma.txt"
|
||||
if [ "${rc}" -eq 0 ]; then
|
||||
echo "::error::integration-magma-split went GREEN under strict. Magma is in lockstep for the whole of P5e (ID-90) and GetFramebufferBindingSlot retires there in P7, so these entries are expected to abort. If P7 has landed, delete this step and fold the entries back into integration-split."
|
||||
exit 1
|
||||
fi
|
||||
marker='Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot@Clear"}'
|
||||
if ! grep -rqF "${marker}" MobileGL/MG_IntegrationTest/p5b-DirectVulkan-*.log 2>/dev/null; then
|
||||
echo "::error::integration-magma-split is red but no private log carries ${marker} - the lane failed for some OTHER reason, which is a defect rather than the expected-red control"
|
||||
grep -rhoE '(Fatal|Admitted)\{[A-Za-z]*, "[^"]*"\}' MobileGL/MG_IntegrationTest/p5b-DirectVulkan-*.log 2>/dev/null | sort | uniq -c | sort -rn | head
|
||||
exit 1
|
||||
fi
|
||||
echo "integration-magma-split is red by design on ${marker}"
|
||||
ctest -L integration-magma-split --no-tests=error -j 2 --output-on-failure
|
||||
|
||||
# ---- P5e (ra2), ID-134 / ID-82: THE CLIENT-ARRAY LANE, EXPECTED RED UNDER RUN-AHEAD ----
|
||||
#
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <Config.h>
|
||||
#include <MG_Remote/Server/ServerLoop.h>
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#endif
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
@@ -712,6 +713,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
auto& state = MG_Pipe::MGPipeApplier();
|
||||
const auto handle = state.VerbStorageBlockProgram;
|
||||
if (handle.Slot >= state.ShaderCsos.size() || !state.ShaderCsos[handle.Slot].Live ||
|
||||
state.ShaderCsos[handle.Slot].Gen != handle.Gen || !storageBlockName) {
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"Magma:storage-block-program-record\"}");
|
||||
std::abort();
|
||||
}
|
||||
auto& record = state.ShaderCsos[handle.Slot];
|
||||
auto found = std::find_if(record.StorageOverrides.begin(), record.StorageOverrides.end(),
|
||||
[&](const auto& entry) { return entry.Name == storageBlockName; });
|
||||
if (found == record.StorageOverrides.end())
|
||||
record.StorageOverrides.push_back({storageBlockName, static_cast<Int32>(storageBlockBinding)});
|
||||
else found->Binding = static_cast<Int32>(storageBlockBinding);
|
||||
++record.BindingsSerial;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
auto* programObject = TryGetDirectVulkanProgram(program);
|
||||
if (!programObject || storageBlockName == nullptr) return;
|
||||
const Int maxBindings =
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
// MobileGL - server-owned program input for Magma (P5f fm).
|
||||
#pragma once
|
||||
|
||||
#include <MG_State/GLState/ProgramState/ProgramObject.h>
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#include <MG_State/GLState/ProgramState/ProgramArtifactsCodec.h>
|
||||
#include "../DirectVulkanResourceState.h"
|
||||
#include <spirv_reflect.h>
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// A borrowed view, valid only while its frontend object or server record lives.
|
||||
// It never constructs a ProgramObject on the server. Post-link mutable values
|
||||
// come from the record tails, not from the archive's link-time defaults.
|
||||
class MagmaProgramSource {
|
||||
public:
|
||||
using Program = MG_State::GLState::ProgramObject;
|
||||
MagmaProgramSource(const Program& program) : m_frontend(&program) {}
|
||||
MagmaProgramSource(MG_Pipe::MGPipeHandle handle, const MG_Pipe::MGPipeShaderCsoRecord& record)
|
||||
: m_handle(handle), m_record(&record) {
|
||||
MOBILEGL_ASSERT(record.Archive != nullptr, "Magma program record has no archive");
|
||||
}
|
||||
|
||||
Bool IsWire() const { return m_record != nullptr; }
|
||||
MG_Pipe::MGPipeHandle Handle() const { return m_handle; }
|
||||
const Program* Frontend() const { return m_frontend; }
|
||||
Uint GetExternalIndex() const { return IsWire() ? m_handle.Slot : m_frontend->GetExternalIndex(); }
|
||||
Uint64 GetLifetimeId() const {
|
||||
return IsWire() ? (Uint64(m_handle.Gen) << 32) | m_handle.Slot : m_frontend->GetLifetimeId();
|
||||
}
|
||||
Bool GetLinkStatus() const { return IsWire() ? m_record->Desc.LinkStatus != 0 : m_frontend->GetLinkStatus(); }
|
||||
Bool GetSpirvStatus() const { return IsWire() ? m_record->Desc.SpirvStatus != 0 : m_frontend->GetSpirvStatus(); }
|
||||
Bool PointSizeDemoted() const { return IsWire() ? m_record->Desc.PointSizeDemoted != 0 : m_frontend->PointSizeDemoted(); }
|
||||
Bool GetSpirvValidationEnabled() const {
|
||||
return IsWire() ? m_record->Desc.EnableSpirvValidation != 0 : m_frontend->GetSpirvValidationEnabled();
|
||||
}
|
||||
const Vector<Vector<Uint>>& GetGeneratedSpirv() const {
|
||||
return IsWire() ? m_record->Archive->Spirv.generatedSpirv : m_frontend->GetGeneratedSpirv();
|
||||
}
|
||||
Vector<ShaderStage> GetLinkedShaderStages() const {
|
||||
if (!IsWire()) return m_frontend->GetLinkedShaderStages();
|
||||
Vector<ShaderStage> result;
|
||||
for (const auto stage : m_record->Archive->LinkedStages) result.push_back(static_cast<ShaderStage>(stage));
|
||||
return result;
|
||||
}
|
||||
Bool HasLinkedShaderStage(ShaderStage stage) const {
|
||||
if (!IsWire()) return m_frontend->HasLinkedShaderStage(stage);
|
||||
const auto& stages = m_record->Archive->LinkedStages;
|
||||
return std::find(stages.begin(), stages.end(), static_cast<Uint32>(stage)) != stages.end();
|
||||
}
|
||||
Bool GetBackendHashMemo(Uint flags, Uint64& hash) const {
|
||||
// A view has no lifetime in which a memo can safely persist. The server
|
||||
// factory's content cache still owns compiled programs; hash from current
|
||||
// archive + binding tails rather than borrowing the client's mutable memo.
|
||||
return !IsWire() && m_frontend->GetBackendHashMemo(flags, hash);
|
||||
}
|
||||
void SetBackendHashMemo(Uint flags, Uint64 hash) const {
|
||||
if (!IsWire()) m_frontend->SetBackendHashMemo(flags, hash);
|
||||
}
|
||||
Uint64 GetBackendStateVersion() const { return IsWire() ? m_record->Serial : m_frontend->GetBackendStateVersion(); }
|
||||
Uint64 GetBlockBindingVersion() const { return IsWire() ? m_record->BindingsSerial : m_frontend->GetBlockBindingVersion(); }
|
||||
Uint64 GetImageUnitVersion() const { return IsWire() ? m_record->BindingsSerial : m_frontend->GetImageUnitVersion(); }
|
||||
Uint64 GetLinkVersion() const { return IsWire() ? m_record->Serial : m_frontend->GetLinkVersion(); }
|
||||
|
||||
const void* GetUBOData() const { return IsWire() ? m_record->GlobalConstants.data() : m_frontend->GetUBOData(); }
|
||||
Uint GetUBOSize() const { return IsWire() ? static_cast<Uint>(m_record->GlobalConstants.size()) : m_frontend->GetUBOSize(); }
|
||||
Uint32 GetUBOContentVersion() const { return IsWire() ? m_record->GlobalConstantsVersion : m_frontend->GetUBOContentVersion(); }
|
||||
Int GetActiveUniformBlocksCount() const {
|
||||
return IsWire() ? static_cast<Int>(Link().glBlockIndexToTProgram.size()) : m_frontend->GetActiveUniformBlocksCount();
|
||||
}
|
||||
Uint GetUniformBlockIndex(const char* name) const {
|
||||
if (!IsWire()) return m_frontend->GetUniformBlockIndex(name);
|
||||
auto found = Link().uniformBlockIndexByName.find(name);
|
||||
if (found == Link().uniformBlockIndexByName.end()) found = Link().uniformBlockIndexByName.find(String(name) + "[0]");
|
||||
return found == Link().uniformBlockIndexByName.end() ? GL_INVALID_INDEX : found->second;
|
||||
}
|
||||
Uint GetUniformBlockBinding(Uint index) const {
|
||||
if (!IsWire()) return m_frontend->GetUniformBlockBinding(index);
|
||||
return index < m_record->BlockBindings.size() ? static_cast<Uint>(m_record->BlockBindings[index]) : 0;
|
||||
}
|
||||
const String& GetUniformBlockName(Uint index) const {
|
||||
if (!IsWire()) return m_frontend->GetUniformBlockName(index);
|
||||
const auto* block = Block(index);
|
||||
static const String empty;
|
||||
return block ? block->name : empty;
|
||||
}
|
||||
Uint GetUBOSizeAt(Uint index) const {
|
||||
if (!IsWire()) return m_frontend->GetUBOSizeAt(index);
|
||||
const auto* block = Block(index);
|
||||
return block ? (static_cast<Uint>(block->size) + 15u) & ~15u : 0;
|
||||
}
|
||||
Bool IsValidUniformLocation(Int location) const {
|
||||
return IsWire() ? Program::IsValidUniformLocation(Link(), location) : m_frontend->IsValidUniformLocation(location);
|
||||
}
|
||||
Bool UniformLocationsAliasSameUniform(Int a, Int b) const {
|
||||
if (!IsWire()) return m_frontend->UniformLocationsAliasSameUniform(a, b);
|
||||
return IsValidUniformLocation(a) && IsValidUniformLocation(b) &&
|
||||
Link().uniformIndexInTProgram[a] == Link().uniformIndexInTProgram[b];
|
||||
}
|
||||
GLenum GetUniformType(Uint location) const {
|
||||
if (!IsWire()) return m_frontend->GetUniformType(location);
|
||||
return IsValidUniformLocation(static_cast<Int>(location))
|
||||
? Program::UniformAtIn(Link(), Link().uniformIndexInTProgram[location]).glDefineType : 0;
|
||||
}
|
||||
Int GetUniformSamplerOrImageUnitIndex(Uint location) const {
|
||||
if (!IsWire()) return m_frontend->GetUniformSamplerOrImageUnitIndex(location);
|
||||
const auto& units = m_record->SamplerUnits;
|
||||
const auto found = std::lower_bound(units.begin(), units.end(), location,
|
||||
[](const MG_Pipe::MGPProgramSamplerUnit& unit, Uint value) { return unit.Location < value; });
|
||||
return found != units.end() && found->Location == location ? found->Unit : -1;
|
||||
}
|
||||
Int GetUniformLocation(const String& name) const {
|
||||
if (!IsWire()) return m_frontend->GetUniformLocation(name);
|
||||
const auto& locations = Link().uniformLocations;
|
||||
auto found = locations.find(name);
|
||||
if (found != locations.end()) return static_cast<Int>(found->second);
|
||||
if (name.empty()) return -1;
|
||||
found = locations.find(name + "[0]");
|
||||
if (found != locations.end()) return static_cast<Int>(found->second);
|
||||
if (name.back() != ']' || name.size() < 4) return -1;
|
||||
const SizeT bracket = name.rfind('[');
|
||||
if (bracket == String::npos || bracket + 1 >= name.size() - 1) return -1;
|
||||
Uint element = 0;
|
||||
for (SizeT i = bracket + 1; i < name.size() - 1; ++i) {
|
||||
if (name[i] < '0' || name[i] > '9') return -1;
|
||||
element = element * 10 + static_cast<Uint>(name[i] - '0');
|
||||
if (element > 0x0fffffffu) return -1;
|
||||
}
|
||||
found = locations.find(name.substr(0, bracket) + "[0]");
|
||||
if (found == locations.end()) found = locations.find(name.substr(0, bracket));
|
||||
if (found == locations.end()) return -1;
|
||||
const Int base = static_cast<Int>(found->second);
|
||||
if (!IsValidUniformLocation(base)) return -1;
|
||||
const Int tIndex = Link().uniformIndexInTProgram[base];
|
||||
if (!Program::UniformAtIn(Link(), tIndex).type.isArray ||
|
||||
static_cast<GLint>(element) >= Program::GetUniformArraySizeByTIndex(Link(), tIndex)) return -1;
|
||||
const Int location = base + static_cast<Int>(element);
|
||||
return UniformLocationsAliasSameUniform(base, location) ? location : -1;
|
||||
}
|
||||
|
||||
SizeT GetTransformFeedbackVaryingCount() const { return IsWire() ? Link().xfbVaryings.size() : m_frontend->GetTransformFeedbackVaryingCount(); }
|
||||
const Vector<MG_State::GLState::XfbVarying>& GetTransformFeedbackVaryings() const {
|
||||
return IsWire() ? Link().xfbVaryings : m_frontend->GetTransformFeedbackVaryings();
|
||||
}
|
||||
SizeT GetTransformFeedbackBufferCount() const { return IsWire() ? Link().xfbStrides.size() : m_frontend->GetTransformFeedbackBufferCount(); }
|
||||
Uint32 GetTransformFeedbackStride(Uint index) const {
|
||||
if (!IsWire()) return m_frontend->GetTransformFeedbackStride(index);
|
||||
return index < Link().xfbStrides.size() ? Link().xfbStrides[index] : 0;
|
||||
}
|
||||
GLenum GetTransformFeedbackBufferMode() const {
|
||||
return IsWire() ? Link().xfbBufferMode : m_frontend->GetTransformFeedbackBufferMode();
|
||||
}
|
||||
Uint GetShaderStorageBlockIndex(const String& name) const {
|
||||
if (!IsWire()) return DirectVulkan::GetShaderStorageBlockIndex(*m_frontend, name);
|
||||
EnsureStorageBlocks();
|
||||
String key = name;
|
||||
auto find = [&] {
|
||||
return std::find_if(m_storageBlocks.begin(), m_storageBlocks.end(),
|
||||
[&](const StorageBlock& block) { return block.Name == key; });
|
||||
};
|
||||
auto found = find();
|
||||
if (found == m_storageBlocks.end() && !name.empty() && name.back() == ']') {
|
||||
const auto bracket = name.rfind('[');
|
||||
if (bracket != String::npos) { key = name.substr(0, bracket); found = find(); }
|
||||
}
|
||||
return found == m_storageBlocks.end() ? GL_INVALID_INDEX : static_cast<Uint>(found - m_storageBlocks.begin());
|
||||
}
|
||||
Uint GetShaderStorageBlockBinding(Uint index) const {
|
||||
if (!IsWire()) return DirectVulkan::GetShaderStorageBlockBinding(*m_frontend, index);
|
||||
EnsureStorageBlocks();
|
||||
if (index >= m_storageBlocks.size()) return 0;
|
||||
const auto& block = m_storageBlocks[index];
|
||||
for (const auto& override : m_record->StorageOverrides) {
|
||||
if (override.Name == block.Name) return override.Binding;
|
||||
}
|
||||
return block.Binding;
|
||||
}
|
||||
|
||||
private:
|
||||
const MG_State::GLState::LinkArtifacts& Link() const { return m_record->Archive->Link; }
|
||||
const MG_State::GLState::BlockReflection* Block(Uint index) const {
|
||||
if (index >= Link().glBlockIndexToTProgram.size()) return nullptr;
|
||||
const Int tIndex = Link().glBlockIndexToTProgram[index];
|
||||
return tIndex >= 0 && static_cast<SizeT>(tIndex) < Link().blockReflection.size()
|
||||
? &Link().blockReflection[tIndex] : nullptr;
|
||||
}
|
||||
struct StorageBlock { String Name; Uint Binding; };
|
||||
void EnsureStorageBlocks() const {
|
||||
if (m_storageBlocksReady) return;
|
||||
m_storageBlocksReady = true;
|
||||
// Match the legacy resource-query index order: stage order, then
|
||||
// normalized descriptor name/binding, deduplicated across stages.
|
||||
for (const auto& spirv : GetGeneratedSpirv()) {
|
||||
if (spirv.empty()) continue;
|
||||
SpvReflectShaderModule module{};
|
||||
if (spvReflectCreateShaderModule(spirv.size() * sizeof(Uint), spirv.data(), &module) != SPV_REFLECT_RESULT_SUCCESS) continue;
|
||||
Uint32 count = 0;
|
||||
if (spvReflectEnumerateDescriptorBindings(&module, &count, nullptr) == SPV_REFLECT_RESULT_SUCCESS) {
|
||||
Vector<SpvReflectDescriptorBinding*> bindings(count);
|
||||
if (spvReflectEnumerateDescriptorBindings(&module, &count, bindings.data()) == SPV_REFLECT_RESULT_SUCCESS) {
|
||||
auto name = [](const SpvReflectDescriptorBinding* binding) {
|
||||
const char* raw = binding->name;
|
||||
if (binding->type_description && binding->type_description->type_name) raw = binding->type_description->type_name;
|
||||
String result = raw ? raw : "";
|
||||
const auto suffix = result.find("[0]");
|
||||
return suffix == String::npos ? result : result.substr(0, suffix);
|
||||
};
|
||||
std::sort(bindings.begin(), bindings.end(), [&](const auto* a, const auto* b) {
|
||||
const String an = a ? name(a) : String(), bn = b ? name(b) : String();
|
||||
return an != bn ? an < bn : (a && b && a->binding < b->binding);
|
||||
});
|
||||
for (const auto* binding : bindings) {
|
||||
if (!binding || binding->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) continue;
|
||||
const String key = name(binding);
|
||||
if (key.empty() || std::any_of(m_storageBlocks.begin(), m_storageBlocks.end(),
|
||||
[&](const StorageBlock& block) { return block.Name == key; })) continue;
|
||||
m_storageBlocks.push_back({key, binding->binding});
|
||||
}
|
||||
}
|
||||
}
|
||||
spvReflectDestroyShaderModule(&module);
|
||||
}
|
||||
}
|
||||
const Program* m_frontend = nullptr;
|
||||
MG_Pipe::MGPipeHandle m_handle = MG_Pipe::kMGPipeNullHandle;
|
||||
const MG_Pipe::MGPipeShaderCsoRecord* m_record = nullptr;
|
||||
mutable Bool m_storageBlocksReady = false;
|
||||
mutable Vector<StorageBlock> m_storageBlocks;
|
||||
};
|
||||
#else
|
||||
using MagmaProgramSource = MG_State::GLState::ProgramObject;
|
||||
#endif
|
||||
}
|
||||
@@ -1891,7 +1891,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
Bool TransformSpirvForXfbCapture(const Vector<Uint>& input, Vector<Uint>& output,
|
||||
const MG_State::GLState::ProgramObject& program) {
|
||||
const MagmaProgramSource& program) {
|
||||
if (input.empty()) {
|
||||
output.clear();
|
||||
return true;
|
||||
@@ -2466,7 +2466,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
ProgramFactory::HashType ProgramFactory::ComputeHash(const MG_State::GLState::ProgramObject& program,
|
||||
ProgramFactory::HashType ProgramFactory::ComputeHash(const MagmaProgramSource& program,
|
||||
CompileOptionFlags flags) const {
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||
// We expect shader stages in program object are sorted
|
||||
@@ -2832,7 +2832,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// key and every element of an array of arrays seeds texture unit 0. Resolving those elements
|
||||
// would then paint silently-wrong pixels with no diagnostic at all - strictly worse than
|
||||
// declining. The decline goes away together with the seeding fix, not before it.
|
||||
static Uint32 DescriptorCountForOpaqueUniformArray(const MG_State::GLState::ProgramObject& program,
|
||||
static Uint32 DescriptorCountForOpaqueUniformArray(const MagmaProgramSource& program,
|
||||
const String& uniformName, Uint32 binding, Int baseLocation,
|
||||
Uint32 reflectedCount, Uint32 maxBindings,
|
||||
const char* kindLabel, Bool& outDeclined) {
|
||||
@@ -2868,7 +2868,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return count;
|
||||
}
|
||||
|
||||
void ProgramFactory::ReflectLayout(const MG_State::GLState::ProgramObject& program,
|
||||
void ProgramFactory::ReflectLayout(const MagmaProgramSource& program,
|
||||
const Vector<Vector<Uint>>& spirv, VkProgramObject& entry) const {
|
||||
// Initialize layout vectors
|
||||
entry.bindingKinds.assign(m_maxBindings, DescriptorBindingKind::None);
|
||||
@@ -3068,7 +3068,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
entry.bindingKinds[binding] = descriptorKind;
|
||||
|
||||
if (descriptorKind == DescriptorBindingKind::StorageBuffer) {
|
||||
const GLuint blockIndex = GetShaderStorageBlockIndex(program, uniformName);
|
||||
const GLuint blockIndex =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
program.GetShaderStorageBlockIndex(uniformName);
|
||||
#else
|
||||
GetShaderStorageBlockIndex(program, uniformName);
|
||||
#endif
|
||||
if (blockIndex == GL_INVALID_INDEX) {
|
||||
MGLOG_D("ProgramFactory::ReflectLayout: skipping inactive SSBO '%s' at binding %u",
|
||||
uniformName.c_str(), binding);
|
||||
@@ -3426,7 +3431,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram(
|
||||
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) {
|
||||
const MagmaProgramSource& program, CompileOptionFlags flags) {
|
||||
// Hashing the full SPIR-V of every stage is far too expensive to repeat per draw;
|
||||
// reuse the program's memoized hash while its backend state version is unchanged.
|
||||
// The memo keys on the flags word, which ComputeHash is no longer a pure function of:
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include "../VkIncludes.h"
|
||||
#include "PipelineFactory.h"
|
||||
#include "MG_State/GLState/ProgramState/ProgramObject.h"
|
||||
#include "MagmaProgramSource.h"
|
||||
#include "MG_State/GLState/ProgramState/ShaderObject.h"
|
||||
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||
|
||||
@@ -471,9 +471,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
~ProgramFactory();
|
||||
ProgramFactory(const ProgramFactory&) = delete;
|
||||
|
||||
HashType ComputeHash(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) const;
|
||||
HashType ComputeHash(const MagmaProgramSource& program, CompileOptionFlags flags) const;
|
||||
const VkProgramObject& GetOrCreateProgram(
|
||||
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
|
||||
const MagmaProgramSource& program, CompileOptionFlags flags);
|
||||
|
||||
// The default framebuffer's current image height, baked as a literal into every
|
||||
// FragCoordYFlip variant (there is no push-constant or specialization channel here, and
|
||||
@@ -600,7 +600,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void ReflectFragmentOutputs(const Vector<ShaderStage>& stages,
|
||||
const Vector<Vector<Uint>>& spirv,
|
||||
VkProgramObject& entry) const;
|
||||
void ReflectLayout(const MG_State::GLState::ProgramObject& program, const Vector<Vector<Uint>>& spirv,
|
||||
void ReflectLayout(const MagmaProgramSource& program, const Vector<Vector<Uint>>& spirv,
|
||||
VkProgramObject& entry) const;
|
||||
// Fills needsPassthroughTessControl / passthroughTessControlEmulatable off the linked
|
||||
// modules. Const and reflection-only: it decides nothing about the pipeline, it only
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
#include "MG_Util/Metrics/TextureMetrics.h"
|
||||
#include "MG_Util/ShaderTranspiler/Types.h"
|
||||
#include <Config.h>
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#endif
|
||||
#include <vulkan/utility/vk_format_utils.h>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
@@ -37,6 +40,162 @@
|
||||
#include <limits>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
[[noreturn]] static void WireDescriptorFatal(const char* detail) {
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"Magma:%s\"}", detail);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
[[noreturn]] static void WireBufferDescriptorDebt() {
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} - Magma buffer descriptors remain P7");
|
||||
std::abort();
|
||||
}
|
||||
|
||||
static VkImageViewType WireViewType(MG_Pipe::MGPipeResourceTarget target) {
|
||||
using T = MG_Pipe::MGPipeResourceTarget;
|
||||
switch (target) {
|
||||
case T::Tex1D: return VK_IMAGE_VIEW_TYPE_1D;
|
||||
case T::Tex1DArray: return VK_IMAGE_VIEW_TYPE_1D_ARRAY;
|
||||
case T::Tex3D: return VK_IMAGE_VIEW_TYPE_3D;
|
||||
case T::TexCube: return VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
case T::TexCubeArray: return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
|
||||
case T::Tex2DArray: case T::Tex2DMSArray: return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
default: return VK_IMAGE_VIEW_TYPE_2D;
|
||||
}
|
||||
}
|
||||
|
||||
static VkComponentSwizzle WireSwizzle(Uint8 value) {
|
||||
switch (static_cast<TextureSwizzleParam>(value)) {
|
||||
case TextureSwizzleParam::Red: return VK_COMPONENT_SWIZZLE_R;
|
||||
case TextureSwizzleParam::Green: return VK_COMPONENT_SWIZZLE_G;
|
||||
case TextureSwizzleParam::Blue: return VK_COMPONENT_SWIZZLE_B;
|
||||
case TextureSwizzleParam::Alpha: return VK_COMPONENT_SWIZZLE_A;
|
||||
case TextureSwizzleParam::Zero: return VK_COMPONENT_SWIZZLE_ZERO;
|
||||
case TextureSwizzleParam::One: return VK_COMPONENT_SWIZZLE_ONE;
|
||||
default: WireDescriptorFatal("sampler-swizzle");
|
||||
}
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveWireImageDescriptor(
|
||||
VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element,
|
||||
Bool storage, VkDescriptorImageInfo& out) const {
|
||||
out = {};
|
||||
auto& state = MG_Pipe::MGPipeApplier();
|
||||
const Int baseLocation = programObj.samplerUniformLocationByBinding[binding];
|
||||
const Int location = baseLocation + static_cast<Int>(element);
|
||||
if (baseLocation < 0 || !program.UniformLocationsAliasSameUniform(baseLocation, location)) return false;
|
||||
const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
|
||||
if (unit < 0 || static_cast<Uint32>(unit) >=
|
||||
(storage ? MG_Pipe::kMGPipeMaxImageUnits : MG_Pipe::kMGPipeMaxTextureUnits)) return false;
|
||||
const auto handle = storage ? state.BoundShaderImages[unit].Res : state.BoundSamplerViews[unit].Texture;
|
||||
// The old placeholder constructor enters the guarded client allocator. Keep that
|
||||
// P7 boundary explicit instead of constructing a frontend texture on the server.
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle)) WireDescriptorFatal("unbound-image-placeholder@P7");
|
||||
if (handle.Slot >= state.TextureResources.size()) WireDescriptorFatal("image-record");
|
||||
const auto& record = state.TextureResources[handle.Slot];
|
||||
if (!record.Live || record.Gen != handle.Gen) WireDescriptorFatal("image-record-generation");
|
||||
auto* resource = m_textureManager->SyncTextureResourceByHandle(handle);
|
||||
if (!resource) WireDescriptorFatal("image-resource");
|
||||
m_textureManager->FlushPendingUploads();
|
||||
|
||||
Uint32 level = storage ? state.BoundShaderImages[unit].Level : record.Params.BaseLevel;
|
||||
Uint32 layer = storage && !state.BoundShaderImages[unit].Layered ? state.BoundShaderImages[unit].Layer : 0;
|
||||
const Uint32 localLevel = level;
|
||||
const Uint32 localLayer = layer;
|
||||
Uint32 layers = 0;
|
||||
VkFormat aliasFormat = VK_FORMAT_UNDEFINED;
|
||||
const auto root = m_textureManager->ResolveWireTextureStorage(handle, level, layer, &aliasFormat, &layers);
|
||||
if (MG_Pipe::MGPipeHandleIsNull(root)) WireDescriptorFatal("image-view-window");
|
||||
const auto target = static_cast<MG_Pipe::MGPipeResourceTarget>(record.Desc.Target);
|
||||
VkImageViewType type = WireViewType(target);
|
||||
if (storage && !state.BoundShaderImages[unit].Layered) {
|
||||
layers = 1;
|
||||
type = target == MG_Pipe::MGPipeResourceTarget::Tex1DArray ? VK_IMAGE_VIEW_TYPE_1D : VK_IMAGE_VIEW_TYPE_2D;
|
||||
}
|
||||
if (type == VK_IMAGE_VIEW_TYPE_3D) layers = 1;
|
||||
Uint32 levels = 1;
|
||||
if (!storage) {
|
||||
if (record.Params.MaxLevel < localLevel || record.Desc.Levels <= localLevel) return false;
|
||||
levels = std::min<Uint32>(record.Desc.Levels - localLevel,
|
||||
record.Params.MaxLevel - localLevel + 1);
|
||||
const auto viewHandle = state.BoundSamplerViews[unit].View;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(viewHandle) && viewHandle.Slot < state.SamplerViewCsos.size()) {
|
||||
const auto& view = state.SamplerViewCsos[viewHandle.Slot];
|
||||
if (!view.Live || view.Gen != viewHandle.Gen || view.View.NumLevels <= localLevel)
|
||||
WireDescriptorFatal("sampler-view-record");
|
||||
levels = std::min<Uint32>(levels, view.View.NumLevels - localLevel);
|
||||
}
|
||||
levels = std::min(levels, resource->mipLevels - level);
|
||||
}
|
||||
VkFormat format = aliasFormat == VK_FORMAT_UNDEFINED ? resource->format : aliasFormat;
|
||||
const auto domain = programObj.samplerNumericDomainByBinding[binding];
|
||||
if (storage) {
|
||||
format = ResolveStorageImageViewFormat(programObj.storageImageFormatByBinding[binding],
|
||||
state.BoundShaderImages[unit].InternalFormat, format,
|
||||
programObj.storageImageUsesBindingFormatByBinding[binding]);
|
||||
} else if (resource->aspect == VK_IMAGE_ASPECT_COLOR_BIT) {
|
||||
format = VkTextureManager::ResolveSampledImageViewFormat(format, domain);
|
||||
}
|
||||
if (format == VK_FORMAT_UNDEFINED) return false;
|
||||
VkImageAspectFlags aspect = resource->aspect;
|
||||
if (!storage && (aspect & VK_IMAGE_ASPECT_DEPTH_BIT))
|
||||
aspect = record.Params.DepthStencilMode == MG_Pipe::kMGPipeDepthStencilModeStencil
|
||||
? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
|
||||
// All wire image descriptors use GENERAL. A texture sampled and image-bound in
|
||||
// the same shader then has one truthful layout, including views of the same owner.
|
||||
if (!VkTextureManager::TransitionImageLayout(commandBuffer, resource->image, resource->layout,
|
||||
VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
resource->layout == VK_IMAGE_LAYOUT_UNDEFINED ? 0 : VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
||||
VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, resource->aspect, 0, resource->mipLevels))
|
||||
WireDescriptorFatal("image-descriptor-transition");
|
||||
// A second descriptor/draw can keep GENERAL after a shader write. A layout
|
||||
// equality fast return is not a memory dependency for that prior write.
|
||||
VkMemoryBarrier memory{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
memory.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
memory.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
0, 1, &memory, 0, nullptr, 0, nullptr);
|
||||
VkImageViewCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
info.image = resource->image;
|
||||
info.viewType = type;
|
||||
info.format = format;
|
||||
info.subresourceRange = {aspect, level, levels, layer, layers};
|
||||
if (!storage) {
|
||||
const Bool alphaIsOne = ResolveTextureFormatInfo(
|
||||
static_cast<TextureInternalFormat>(record.Desc.InternalFormat)).expandRgbToRgba;
|
||||
const auto swizzle = [&](Uint32 channel) {
|
||||
const auto value = record.Params.Swizzle[channel];
|
||||
return alphaIsOne && static_cast<TextureSwizzleParam>(value) == TextureSwizzleParam::Alpha
|
||||
? VK_COMPONENT_SWIZZLE_ONE : WireSwizzle(value);
|
||||
};
|
||||
info.components = {swizzle(0), swizzle(1), swizzle(2), swizzle(3)};
|
||||
}
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
if (vkCreateImageView(m_device, &info, nullptr, &view) != VK_SUCCESS) return false;
|
||||
m_frames[m_wireFrameIndex].wireImageViews.push_back(view);
|
||||
out.imageView = view;
|
||||
out.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
if (storage) {
|
||||
if (state.BoundShaderImages[unit].Access != kMGPipeImageAccessReadOnly)
|
||||
m_textureManager->MarkWireTextureGpuWritten(handle, localLevel, localLayer, layers);
|
||||
} else {
|
||||
auto samplerHandle = state.BoundSamplerStates[unit];
|
||||
if (MG_Pipe::MGPipeHandleIsNull(samplerHandle)) samplerHandle = record.Params.BuiltinSampler;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(samplerHandle) || samplerHandle.Slot >= state.SamplerCsos.size())
|
||||
WireDescriptorFatal("sampler-record");
|
||||
const auto& sampler = state.SamplerCsos[samplerHandle.Slot];
|
||||
if (!sampler.Live || sampler.Gen != samplerHandle.Gen) WireDescriptorFatal("sampler-record-generation");
|
||||
out.sampler = m_samplerManager->GetOrCreateSamplerFromParameters(sampler.Params,
|
||||
static_cast<TextureInternalFormat>(record.Desc.InternalFormat),
|
||||
domain == SamplerNumericDomain::SignedInteger || domain == SamplerNumericDomain::UnsignedInteger,
|
||||
levels);
|
||||
if (!out.sampler) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
namespace {
|
||||
constexpr Uint kFallbackTexture2DExternalIndex = 0xFFFFFF00u;
|
||||
// One id for every storage-image placeholder. They are never reachable through GL - no
|
||||
@@ -234,7 +393,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// per array element, so the element's location is the base plus its index - bounded by the
|
||||
// array's real extent so a descriptorCount that outran the reflection cannot walk onto the
|
||||
// next uniform. Element 0 is the ordinary non-array case and costs nothing extra.
|
||||
static Int ResolveDescriptorElementLocation(const MG_State::GLState::ProgramObject& program, Int baseLocation,
|
||||
static Int ResolveDescriptorElementLocation(const MagmaProgramSource& program, Int baseLocation,
|
||||
Uint32 element) {
|
||||
if (baseLocation < 0 || element == 0) {
|
||||
return baseLocation;
|
||||
@@ -252,7 +411,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
: 1u;
|
||||
}
|
||||
|
||||
static Int ResolveSamplerUnitIndex(const MG_State::GLState::ProgramObject& program, Int location, Uint32 binding) {
|
||||
static Int ResolveSamplerUnitIndex(const MagmaProgramSource& program, Int location, Uint32 binding) {
|
||||
MOBILEGL_ASSERT(location >= -1, "ResolveSamplerUnitIndex: invalid sampler location for binding %u", binding);
|
||||
if (location < 0) {
|
||||
return 0;
|
||||
@@ -349,6 +508,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_unboundStorageImageTextures.clear();
|
||||
for (auto& frame : m_frames) {
|
||||
if (m_device != VK_NULL_HANDLE) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
for (const auto view : frame.wireImageViews) vkDestroyImageView(m_device, view, nullptr);
|
||||
frame.wireImageViews.clear();
|
||||
#endif
|
||||
for (auto& view : frame.texelBufferViews) {
|
||||
if (view != VK_NULL_HANDLE) {
|
||||
vkDestroyBufferView(m_device, view, nullptr);
|
||||
@@ -390,6 +553,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void UniformManager::BeginFrame(Uint32 frameIndex) {
|
||||
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "UniformDescriptorBinder::BeginFrame invalid frame index");
|
||||
auto& frame = m_frames[frameIndex];
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
m_wireFrameIndex = frameIndex;
|
||||
for (const auto view : frame.wireImageViews) vkDestroyImageView(m_device, view, nullptr);
|
||||
frame.wireImageViews.clear();
|
||||
#endif
|
||||
for (auto& view : frame.texelBufferViews) {
|
||||
if (view != VK_NULL_HANDLE) {
|
||||
vkDestroyBufferView(m_device, view, nullptr);
|
||||
@@ -467,11 +635,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 binding, Uint32 element,
|
||||
VkDescriptorImageInfo& outImageInfo,
|
||||
Bool trustUnchangedHint) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) return ResolveWireImageDescriptor(commandBuffer, program, programObj, binding, element, false, outImageInfo);
|
||||
#endif
|
||||
MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptor: texture manager is null");
|
||||
MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptor: sampler manager is null");
|
||||
// The whole-descriptor memo below is keyed by binding alone, so it describes a binding
|
||||
@@ -753,7 +924,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
Bool UniformManager::ProgramSamplesOnlySingleLevelTextures(
|
||||
const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj) {
|
||||
const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& programObj) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) return false; // Conservative optimization gate; no frontend probe.
|
||||
#endif
|
||||
// A declined program never draws (see VkProgramObject::declinedDescriptors), and its
|
||||
// declined binding has no resolvable uniform location - so there is nothing to prove
|
||||
// about the textures it would have sampled.
|
||||
@@ -810,7 +984,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return sawSampler;
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
|
||||
Bool UniformManager::ResolveSamplerTexture(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
||||
outTexture.reset();
|
||||
@@ -837,7 +1011,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
MG_State::GLState::ITextureObject* UniformManager::ResolveSamplerTextureRaw(
|
||||
const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj,
|
||||
const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 binding, Uint32 element) {
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE, "ResolveSamplerTextureRaw: GL context is null");
|
||||
MOBILEGL_ASSERT(binding < programObj.samplerUniformLocationByBinding.size(),
|
||||
@@ -864,10 +1038,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return texture;
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||
Bool UniformManager::ResolveTexelBufferDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 binding, Uint32 frameIndex,
|
||||
VkBufferView& outBufferView) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) {
|
||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
||||
const Int unit = location < 0 ? 0 : program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
|
||||
const auto& state = MG_Pipe::MGPipeApplier();
|
||||
if (unit < 0 || static_cast<Uint32>(unit) >= MG_Pipe::kMGPipeMaxTextureUnits) return false;
|
||||
const auto handle = state.BoundSamplerViews[unit].Texture;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(handle)) {
|
||||
if (handle.Slot >= state.TextureResources.size()) WireDescriptorFatal("texel-buffer-record");
|
||||
const auto& record = state.TextureResources[handle.Slot];
|
||||
if (!record.Live || record.Gen != handle.Gen) WireDescriptorFatal("texel-buffer-generation");
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(record.Desc.BufferForTexBuffer)) WireBufferDescriptorDebt();
|
||||
}
|
||||
outBufferView = AcquireUnboundTexelBufferView(VK_FORMAT_UNDEFINED,
|
||||
programObj.samplerNumericDomainByBinding[binding], false);
|
||||
return outBufferView != VK_NULL_HANDLE;
|
||||
}
|
||||
#endif
|
||||
outBufferView = VK_NULL_HANDLE;
|
||||
MOBILEGL_ASSERT(m_bufferManager != nullptr, "ResolveTexelBufferDescriptor: buffer manager is null");
|
||||
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "ResolveTexelBufferDescriptor: frame index out of range");
|
||||
@@ -988,10 +1180,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// binding at all for a uniform the shader still read, and lavapipe segfaulted inside pipeline
|
||||
// creation on the JIT worker thread. KHR-GL44.multi_bind.dispatch_bind_image_textures is the
|
||||
// case that carries it.
|
||||
Bool UniformManager::ResolveStorageTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||
Bool UniformManager::ResolveStorageTexelBufferDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 binding, Uint32 frameIndex,
|
||||
VkBufferView& outBufferView) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) {
|
||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
||||
const Int unit = location < 0 ? 0 : program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
|
||||
const auto& state = MG_Pipe::MGPipeApplier();
|
||||
if (unit < 0 || static_cast<Uint32>(unit) >= MG_Pipe::kMGPipeMaxImageUnits) return false;
|
||||
const auto handle = state.BoundShaderImages[unit].Res;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(handle)) {
|
||||
if (handle.Slot >= state.TextureResources.size()) WireDescriptorFatal("texel-buffer-record");
|
||||
const auto& record = state.TextureResources[handle.Slot];
|
||||
if (!record.Live || record.Gen != handle.Gen) WireDescriptorFatal("texel-buffer-generation");
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(record.Desc.BufferForTexBuffer)) WireBufferDescriptorDebt();
|
||||
}
|
||||
outBufferView = AcquireUnboundTexelBufferView(programObj.storageImageFormatByBinding[binding],
|
||||
programObj.samplerNumericDomainByBinding[binding], true);
|
||||
return outBufferView != VK_NULL_HANDLE;
|
||||
}
|
||||
#endif
|
||||
outBufferView = VK_NULL_HANDLE;
|
||||
MOBILEGL_ASSERT(m_bufferManager != nullptr, "ResolveStorageTexelBufferDescriptor: buffer manager is null");
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE, "ResolveStorageTexelBufferDescriptor: GL context is null");
|
||||
@@ -1154,7 +1364,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveStorageBufferDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||
Bool UniformManager::ResolveStorageBufferDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 binding, Uint32 element,
|
||||
VkDescriptorBufferInfo& outBufferInfo) const {
|
||||
@@ -1195,7 +1405,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const GLuint frontendBinding =
|
||||
isAtomicCounterBlock
|
||||
? static_cast<GLuint>(atomicCounterBinding)
|
||||
: GetShaderStorageBlockBinding(program, static_cast<GLuint>(blockIndex)) + element;
|
||||
:
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
(program.IsWire() ? program.GetShaderStorageBlockBinding(static_cast<GLuint>(blockIndex)) :
|
||||
GetShaderStorageBlockBinding(*program.Frontend(), static_cast<GLuint>(blockIndex))) + element;
|
||||
#else
|
||||
GetShaderStorageBlockBinding(program, static_cast<GLuint>(blockIndex)) + element;
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) {
|
||||
const auto& state = MG_Pipe::MGPipeApplier();
|
||||
const Uint32 cls = isAtomicCounterBlock ? 2u : 1u;
|
||||
if (frontendBinding < state.ShaderBufferCount[cls] &&
|
||||
!MG_Pipe::MGPipeHandleIsNull(state.BoundShaderBuffers[cls][frontendBinding].Res))
|
||||
WireBufferDescriptorDebt();
|
||||
const BufferSlice placeholder = m_bufferManager->AcquireUnboundStorageDescriptor();
|
||||
if (!placeholder.IsValid()) return false;
|
||||
outBufferInfo = {placeholder.buffer, placeholder.offset, placeholder.size};
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
const Uint32 bindingPointCount =
|
||||
static_cast<Uint32>(MGB_CTX->GetBufferBindingPointCount(bufferTarget));
|
||||
MOBILEGL_ASSERT(frontendBinding < bindingPointCount,
|
||||
@@ -1274,10 +1503,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveStorageImageDescriptor(VkCommandBuffer commandBuffer,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 binding, Uint32 element,
|
||||
VkDescriptorImageInfo& outImageInfo) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) return ResolveWireImageDescriptor(commandBuffer, program, programObj, binding, element, true, outImageInfo);
|
||||
#endif
|
||||
outImageInfo = {};
|
||||
MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveStorageImageDescriptor: texture manager is null");
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE, "ResolveStorageImageDescriptor: GL context is null");
|
||||
@@ -1657,7 +1889,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return texture;
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveSampledBinding(const MG_State::GLState::ProgramObject& program,
|
||||
Bool UniformManager::ResolveSampledBinding(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 binding, Uint32 element,
|
||||
MG_State::GLState::ITextureObject*& outTexture,
|
||||
@@ -1726,10 +1958,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool UniformManager::CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
|
||||
Bool UniformManager::CollectSampledTextures(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Vector<MG_State::GLState::ITextureObject*>& outTextures,
|
||||
Vector<SampledBindingRecord>* outBindingRecords) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) { outTextures.clear(); if (outBindingRecords) outBindingRecords->clear(); return true; }
|
||||
#endif
|
||||
outTextures.clear();
|
||||
if (outBindingRecords != nullptr) {
|
||||
outBindingRecords->clear();
|
||||
@@ -1773,9 +2008,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool UniformManager::SampledBindingsUnchanged(const MG_State::GLState::ProgramObject& program,
|
||||
Bool UniformManager::SampledBindingsUnchanged(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
const Vector<SampledBindingRecord>& previousRecords) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) return false;
|
||||
#endif
|
||||
// A declined program takes the full path every time and is refused there.
|
||||
if (programObj.declinedDescriptors) {
|
||||
return false;
|
||||
@@ -1816,9 +2054,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
Bool UniformManager::CollectStorageImageTextures(
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Vector<MG_State::GLState::ITextureObject*>& outTextures) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) { outTextures.clear(); return true; }
|
||||
#endif
|
||||
outTextures.clear();
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE,
|
||||
"CollectStorageImageTextures: GL context is null");
|
||||
@@ -1898,9 +2139,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
Bool UniformManager::CollectSamplerImageFeedback(
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Vector<SamplerImageFeedbackBinding>& outBindings) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) { outBindings.clear(); return true; }
|
||||
#endif
|
||||
outBindings.clear();
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE,
|
||||
"CollectSamplerImageFeedback: GL context is null");
|
||||
@@ -1976,7 +2220,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program,
|
||||
Bool UniformManager::ResolveUniformBufferPayload(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 arrayElement, UboBindResult& out) const {
|
||||
const void* outData = nullptr;
|
||||
@@ -2002,6 +2246,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return outData != nullptr && outSize > 0;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Global uniforms above are record bytes; named UBOs retain P7's buffer boundary.
|
||||
if (program.IsWire()) WireBufferDescriptorDebt();
|
||||
#endif
|
||||
|
||||
MOBILEGL_ASSERT(binding < programObj.uniformBlockIndexByBinding.size(),
|
||||
"ResolveUniformBufferPayload: UBO mapping binding %u out of range", binding);
|
||||
Int blockIndex = programObj.uniformBlockIndexByBinding[binding];
|
||||
@@ -2265,7 +2514,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveDynamicUboDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||
Bool UniformManager::ResolveDynamicUboDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 binding, Uint32 arrayElement, Uint32 frameIndex,
|
||||
VkBuffer& outBuffer, VkDeviceSize& outRange,
|
||||
@@ -2361,7 +2610,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
Bool UniformManager::BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 frameIndex,
|
||||
VkPipelineBindPoint bindPoint,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ProgramFactory.h"
|
||||
#include "MagmaProgramSource.h"
|
||||
#include "VkBufferManager.h"
|
||||
#include "VkSamplerManager.h"
|
||||
#include "VkTextureManager.h"
|
||||
@@ -78,7 +79,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Uint64 textureLifetimeId = 0;
|
||||
Uint64 samplerLifetimeId = 0;
|
||||
};
|
||||
Bool CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
|
||||
Bool CollectSampledTextures(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Vector<MG_State::GLState::ITextureObject*>& outTextures,
|
||||
Vector<SampledBindingRecord>* outBindingRecords = nullptr);
|
||||
@@ -87,14 +88,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// (texture, effective sampler) pair. A texture bind generation bump alone (e.g. a
|
||||
// redundant glBindSampler, which always bumps it) does not prove the sampled set
|
||||
// moved; this walk does, without rebuilding the set or falling off the fast path.
|
||||
Bool SampledBindingsUnchanged(const MG_State::GLState::ProgramObject& program,
|
||||
Bool SampledBindingsUnchanged(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
const Vector<SampledBindingRecord>& previousRecords) const;
|
||||
Bool CollectStorageImageTextures(const MG_State::GLState::ProgramObject& program,
|
||||
Bool CollectStorageImageTextures(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Vector<MG_State::GLState::ITextureObject*>& outTextures) const;
|
||||
Bool CollectSamplerImageFeedback(
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Vector<SamplerImageFeedbackBinding>& outBindings) const;
|
||||
static Bool SamplerOverlapsWritableImageSubresource(Int samplerBaseLevel, Int samplerMaxLevel,
|
||||
@@ -106,7 +107,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// epochs AND per-resource layout values - so the per-binding cached
|
||||
// VkDescriptorImageInfo may be reused without re-running the resolve chain.
|
||||
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 frameIndex,
|
||||
VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
@@ -127,7 +128,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// ExplicitLod0Sampling SPIR-V rewrite safe to request. Deliberately conservative: it reads
|
||||
// only GL state, so a texture that ends up single-level for another reason (one uploaded
|
||||
// level under a wide level range) merely misses the rewrite.
|
||||
static Bool ProgramSamplesOnlySingleLevelTextures(const MG_State::GLState::ProgramObject& program,
|
||||
static Bool ProgramSamplesOnlySingleLevelTextures(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj);
|
||||
|
||||
private:
|
||||
@@ -155,12 +156,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Vector<DescriptorPoolBucket> descriptorPools;
|
||||
UnorderedMap<VkDescriptorSetLayout, DescriptorSetCacheEntry> descriptorSetCacheByLayout;
|
||||
Vector<VkBufferView> texelBufferViews;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
mutable Vector<VkImageView> wireImageViews;
|
||||
#endif
|
||||
Uint32 activeDescriptorPoolIndex = 0;
|
||||
Uint32 allocatedSetsThisFrame = 0;
|
||||
Uint32 peakAllocatedSetsThisFrame = 0;
|
||||
};
|
||||
|
||||
static Bool ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
|
||||
static Bool ResolveSamplerTexture(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
|
||||
// Shared per-binding resolution for CollectSampledTextures and
|
||||
@@ -169,7 +173,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// exists), effective sampler = unit override else the texture's own sampler.
|
||||
// False = the binding is skipped (unbound with a non-2D fallback target).
|
||||
// `element` indexes a sampler array inside the binding; see ResolveSamplerDescriptor.
|
||||
Bool ResolveSampledBinding(const MG_State::GLState::ProgramObject& program,
|
||||
Bool ResolveSampledBinding(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element,
|
||||
MG_State::GLState::ITextureObject*& outTexture,
|
||||
const MG_State::GLState::SamplerObject*& outSampler) const;
|
||||
@@ -177,7 +181,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// the bound texture stays alive through the draw via GL binding state, so callers that
|
||||
// only need the pointer skip the SharedPtr copy's atomic refcount churn.
|
||||
static MG_State::GLState::ITextureObject* ResolveSamplerTextureRaw(
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element);
|
||||
// `numericDomain` is the sampler's class, and it matters only for the multisample arm -
|
||||
// see GetFallbackMultisampleTexture for why the single-sampled fallback can ignore it.
|
||||
@@ -224,13 +228,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// (see BindProgramUniformBuffers' samplerDescriptorsUnchangedHint for the proof
|
||||
// obligations the caller carries). The cache is keyed by binding alone, so it is
|
||||
// used ONLY for single-descriptor bindings - see m_samplerResolveMemo.
|
||||
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
|
||||
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 element, VkDescriptorImageInfo& outImageInfo,
|
||||
Bool trustUnchangedHint = false) const;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Bool ResolveWireImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 element, Bool storage, VkDescriptorImageInfo& out) const;
|
||||
Uint32 m_wireFrameIndex = 0;
|
||||
#endif
|
||||
Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride,
|
||||
VkDescriptorImageInfo& outImageInfo) const;
|
||||
Bool ResolveTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||
Bool ResolveTexelBufferDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 frameIndex, VkBufferView& outBufferView);
|
||||
// GLSL `imageBuffer`: the same VkBufferView descriptor as the sampled texel buffer above,
|
||||
@@ -238,19 +248,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// made GPU-resident-writable because the shader may store to it. No `element` parameter:
|
||||
// an imageBuffer ARRAY is refused at program creation, so a binding is always one
|
||||
// descriptor (see the array gate in RemapDescriptorBindingsForVulkan).
|
||||
Bool ResolveStorageTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||
Bool ResolveStorageTexelBufferDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 frameIndex, VkBufferView& outBufferView);
|
||||
// `element` indexes a block INSTANCE array's descriptors; it is 0 for every ordinary
|
||||
// block. Each element resolves through its own GL storage block, and so its own GL
|
||||
// binding point, buffer and glBindBufferRange window.
|
||||
Bool ResolveStorageBufferDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||
Bool ResolveStorageBufferDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 element, VkDescriptorBufferInfo& outBufferInfo) const;
|
||||
// `element` indexes an image ARRAY inside one binding; each element carries its own
|
||||
// independently assigned GL image unit.
|
||||
Bool ResolveStorageImageDescriptor(VkCommandBuffer commandBuffer,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 element, VkDescriptorImageInfo& outImageInfo) const;
|
||||
// Result of resolving a UBO binding: either a zero-copy direct bind to the app's resident
|
||||
@@ -263,14 +273,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const void* payload = nullptr; // fallback UploadTransient path
|
||||
VkDeviceSize payloadSize = 0;
|
||||
};
|
||||
Bool ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program,
|
||||
Bool ResolveUniformBufferPayload(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 arrayElement, UboBindResult& out) const;
|
||||
// Shared resolution of one dynamic-UBO binding element into the
|
||||
// (buffer, range, dynamicOffset) triple the descriptor consumes: direct
|
||||
// bind, global-slice reuse, or transient upload. Used by the full walk
|
||||
// and by the dynamic-offset-only rebind (see FastRebindMemo).
|
||||
Bool ResolveDynamicUboDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||
Bool ResolveDynamicUboDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 arrayElement, Uint32 frameIndex, VkBuffer& outBuffer,
|
||||
VkDeviceSize& outRange, Uint32& outDynamicOffset);
|
||||
|
||||
@@ -15,7 +15,37 @@
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// An accessor view over the wire's POD values, not a GL object with constructor,
|
||||
// lifetime ID, allocator hooks or mutation callbacks. Both sources below use
|
||||
// one sampler policy, including format-sensitive border-color conversion.
|
||||
struct SamplerParametersSource {
|
||||
const SamplerParameters& Params;
|
||||
SamplerWrapMode GetWrapS() const { return Params.wrapS; }
|
||||
SamplerWrapMode GetWrapT() const { return Params.wrapT; }
|
||||
SamplerWrapMode GetWrapR() const { return Params.wrapR; }
|
||||
SamplerFilterMode GetMinFilter() const { return Params.minFilter; }
|
||||
SamplerFilterMode GetMagFilter() const { return Params.magFilter; }
|
||||
SamplerMipmapMode GetMipmapMode() const { return Params.mipmapMode; }
|
||||
Float GetMinLod() const { return Params.minLod; }
|
||||
Float GetMaxLod() const { return Params.maxLod; }
|
||||
Float GetLodBias() const { return Params.lodBias; }
|
||||
Float GetMaxAnisotropy() const { return Params.maxAnisotropy; }
|
||||
SamplerCompareMode GetCompareMode() const { return Params.compareMode; }
|
||||
SamplerCompareFunc GetSamplerCompareFunc() const { return Params.compareFunc; }
|
||||
const FloatVec4& GetBorderColor() const { return Params.borderColor; }
|
||||
const IntVec4& GetBorderColorI() const { return Params.borderColorI; }
|
||||
const UintVec4& GetBorderColorUI() const { return Params.borderColorUI; }
|
||||
// Cache identity is entirely content-based; these fields are diagnostics.
|
||||
Uint GetExternalIndex() const { return 0; }
|
||||
Uint16 GetVersion() const { return 0; }
|
||||
};
|
||||
|
||||
template <class SamplerSource>
|
||||
Bool UsesBorderColor(const SamplerSource& sampler) {
|
||||
#else
|
||||
Bool UsesBorderColor(const MG_State::GLState::SamplerObject& sampler) {
|
||||
#endif
|
||||
return sampler.GetWrapS() == SamplerWrapMode::ClampToBorder ||
|
||||
sampler.GetWrapT() == SamplerWrapMode::ClampToBorder ||
|
||||
sampler.GetWrapR() == SamplerWrapMode::ClampToBorder;
|
||||
@@ -191,14 +221,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return std::fabs(lhs - rhs) <= 1e-6f;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
template <class SamplerSource>
|
||||
Float ResolveEffectiveMaxLod(const SamplerSource& sampler) {
|
||||
#else
|
||||
Float ResolveEffectiveMaxLod(const MG_State::GLState::SamplerObject& sampler) {
|
||||
#endif
|
||||
if (sampler.GetMipmapMode() == SamplerMipmapMode::None) {
|
||||
return 0.0f;
|
||||
}
|
||||
return sampler.GetMaxLod();
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
template <class SamplerSource>
|
||||
Float ResolveEffectiveMinLod(const SamplerSource& sampler, Float effectiveMaxLod) {
|
||||
#else
|
||||
Float ResolveEffectiveMinLod(const MG_State::GLState::SamplerObject& sampler, Float effectiveMaxLod) {
|
||||
#endif
|
||||
return std::min(sampler.GetMinLod(), effectiveMaxLod);
|
||||
}
|
||||
|
||||
@@ -209,7 +249,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// GL's non-mipmapped minification - large enough for lambda to stay positive, small enough
|
||||
// that a NEAREST mip mode still rounds down to level 0. Clamped rather than assigned, so a
|
||||
// texture whose GL_TEXTURE_MAX_LOD really is 0 keeps magnifying as GL says it must.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
template <class SamplerSource>
|
||||
Float ResolveSingleLevelMaxLod(const SamplerSource& sampler, Bool singleLevelView) {
|
||||
#else
|
||||
Float ResolveSingleLevelMaxLod(const MG_State::GLState::SamplerObject& sampler, Bool singleLevelView) {
|
||||
#endif
|
||||
const Float maxLod = ResolveEffectiveMaxLod(sampler);
|
||||
return singleLevelView ? std::min(maxLod, 0.25f) : maxLod;
|
||||
}
|
||||
@@ -230,7 +275,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
template <class SamplerSource>
|
||||
Float VkSamplerManager::ResolveEffectiveMaxAnisotropy(const SamplerSource& sampler,
|
||||
#else
|
||||
Float VkSamplerManager::ResolveEffectiveMaxAnisotropy(const MG_State::GLState::SamplerObject& sampler,
|
||||
#endif
|
||||
Bool forceNearestFiltering) const {
|
||||
if (!m_samplerAnisotropySupported) return 1.0f;
|
||||
if (forceNearestFiltering) return 1.0f;
|
||||
@@ -289,7 +339,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
template <class SamplerSource>
|
||||
Uint64 VkSamplerManager::BuildSamplerKey(const SamplerSource& sampler,
|
||||
#else
|
||||
Uint64 VkSamplerManager::BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
||||
#endif
|
||||
Bool forceNearestFiltering, Bool singleLevelView,
|
||||
const ResolvedBorderColor& borderColor) const {
|
||||
MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null");
|
||||
@@ -335,8 +390,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return XXH64_digest(m_hashState);
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
template <class SamplerSource>
|
||||
VkSampler VkSamplerManager::GetOrCreateSamplerImpl(const SamplerSource& sampler,
|
||||
TextureInternalFormat format,
|
||||
#else
|
||||
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
#endif
|
||||
Bool forceNearestFiltering, Uint32 viewLevelCount) {
|
||||
// A view that exposes a single mip level has no second level to blend with, so GL's
|
||||
// *_MIPMAP_* minification filters degenerate to plain filtering on the base level -
|
||||
@@ -348,7 +409,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// the default-framebuffer blit shader had to work around with an explicit-LOD sample.
|
||||
const Bool singleLevelView = viewLevelCount == 1;
|
||||
// Resolved once and used for both the key and the create-info; see ResolvedBorderColor.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const ResolvedBorderColor borderColor = ResolveBorderColor(sampler, format);
|
||||
#else
|
||||
const ResolvedBorderColor borderColor = ResolveBorderColor(sampler, texture);
|
||||
#endif
|
||||
const Uint64 key = BuildSamplerKey(sampler, forceNearestFiltering, singleLevelView, borderColor);
|
||||
auto it = m_samplers.find(key);
|
||||
if (it != m_samplers.end()) {
|
||||
@@ -408,6 +473,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return vkSampler;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
Bool forceNearestFiltering, Uint32 viewLevelCount) {
|
||||
return GetOrCreateSamplerImpl(sampler, texture.GetFormat(), forceNearestFiltering, viewLevelCount);
|
||||
}
|
||||
|
||||
VkSampler VkSamplerManager::GetOrCreateSamplerFromParameters(const SamplerParameters& parameters,
|
||||
TextureInternalFormat format, Bool forceNearestFiltering, Uint32 viewLevelCount) {
|
||||
return GetOrCreateSamplerImpl(SamplerParametersSource{parameters}, format,
|
||||
forceNearestFiltering, viewLevelCount);
|
||||
}
|
||||
#endif
|
||||
|
||||
VkFilter VkSamplerManager::ToVkFilter(SamplerFilterMode mode) {
|
||||
return mode == SamplerFilterMode::Nearest ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
||||
}
|
||||
@@ -463,8 +542,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
template <class SamplerSource>
|
||||
VkSamplerManager::ResolvedBorderColor VkSamplerManager::ResolveBorderColor(
|
||||
const SamplerSource& sampler, TextureInternalFormat format) const {
|
||||
#else
|
||||
VkSamplerManager::ResolvedBorderColor VkSamplerManager::ResolveBorderColor(
|
||||
const MG_State::GLState::SamplerObject& sampler, const MG_State::GLState::ITextureObject& texture) const {
|
||||
#endif
|
||||
ResolvedBorderColor resolved{};
|
||||
if (!UsesBorderColor(sampler)) {
|
||||
return resolved; // FLOAT_TRANSPARENT_BLACK, never sampled
|
||||
@@ -472,7 +557,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
// Border colour is sampler state: a bound sampler object supplies its own, and a texture
|
||||
// with none reaches the very same value through the sampler object it owns.
|
||||
#if !MOBILEGL_BUILD_DISAGGREGATED
|
||||
const auto format = texture.GetFormat();
|
||||
#endif
|
||||
const auto domain = ResolveBorderColorDomain(format);
|
||||
const Bool canUseCustom = m_customBorderColorSupported && m_maxCustomBorderColorSamplers > 0 &&
|
||||
m_customBorderColorSamplerCount < m_maxCustomBorderColorSamplers;
|
||||
|
||||
@@ -46,6 +46,12 @@ public:
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
Bool forceNearestFiltering = false,
|
||||
Uint32 viewLevelCount = 0);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// The applier owns these values: effective bound/builtin sampler parameters and
|
||||
// the sampled texture view's format/range. No frontend object is constructed.
|
||||
VkSampler GetOrCreateSamplerFromParameters(const SamplerParameters& parameters,
|
||||
TextureInternalFormat format, Bool forceNearestFiltering = false, Uint32 viewLevelCount = 0);
|
||||
#endif
|
||||
// Frame boundary hook: ages the sampler cache and destroys samplers not used
|
||||
// for many frames. The key hashes continuous float state (lodBias, LOD clamps,
|
||||
// anisotropy), so an app animating those would otherwise mint an unbounded
|
||||
@@ -86,19 +92,37 @@ private:
|
||||
Bool usesCustomBorderColor = false;
|
||||
};
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
template <class SamplerSource>
|
||||
VkSampler GetOrCreateSamplerImpl(const SamplerSource& sampler, TextureInternalFormat format,
|
||||
Bool forceNearestFiltering, Uint32 viewLevelCount);
|
||||
template <class SamplerSource>
|
||||
Uint64 BuildSamplerKey(const SamplerSource& sampler, Bool forceNearestFiltering,
|
||||
#else
|
||||
Uint64 BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler, Bool forceNearestFiltering,
|
||||
#endif
|
||||
Bool singleLevelView, const ResolvedBorderColor& borderColor) const;
|
||||
static VkFilter ToVkFilter(SamplerFilterMode mode);
|
||||
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
|
||||
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
|
||||
static VkCompareOp ToVkCompareOp(SamplerCompareFunc func);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
template <class SamplerSource>
|
||||
ResolvedBorderColor ResolveBorderColor(const SamplerSource& sampler, TextureInternalFormat format) const;
|
||||
#else
|
||||
ResolvedBorderColor ResolveBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture) const;
|
||||
#endif
|
||||
// The anisotropy Vulkan will actually apply: 1.0 (i.e. disabled) unless the feature is on and
|
||||
// the sampler filters linearly both ways, otherwise the GL request clamped to the device limit.
|
||||
// GL happily carries GL_TEXTURE_MAX_ANISOTROPY on a NEAREST sampler (Blaze3D's blocks do exactly
|
||||
// that) while Vulkan forbids anisotropyEnable there, so the GL value must never be forwarded raw.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
template <class SamplerSource>
|
||||
Float ResolveEffectiveMaxAnisotropy(const SamplerSource& sampler,
|
||||
#else
|
||||
Float ResolveEffectiveMaxAnisotropy(const MG_State::GLState::SamplerObject& sampler,
|
||||
#endif
|
||||
Bool forceNearestFiltering) const;
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,9 +17,21 @@
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <Config.h>
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// The handle-keyed arm's key type. Header-only POD, disagg-only, exactly the shape
|
||||
// MagmaPipeArms.h already gives this directory.
|
||||
#include <MG_Pipe/MGPipeHandles.h>
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class ITextureObject;
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
namespace MobileGL::MG_Pipe {
|
||||
struct MGPipeResourceRecord;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
enum class SamplerNumericDomain : Uint8;
|
||||
@@ -333,6 +345,13 @@ public:
|
||||
// shape version and nothing else. Without this in the early-out key the image, its views
|
||||
// and therefore imageSize() all keep answering with the texture's PREVIOUS shape.
|
||||
Uint64 syncedShapeVersion = 0;
|
||||
// The handle-keyed arm's (P5f fm) early-out key: the applier record's server-owned
|
||||
// Serial at the last successful sync. 0 also means "this resource has only ever been
|
||||
// synced by the frontend arm" - a wire record's Serial starts at 0 on a create and moves
|
||||
// on every mutation, so the first handle sync of a fresh image always re-checks.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Uint64 syncedWireSerial = 0;
|
||||
#endif
|
||||
|
||||
TextureResource() = default;
|
||||
TextureResource(const TextureResource&) = delete;
|
||||
@@ -365,6 +384,9 @@ public:
|
||||
std::swap(this->syncedContentVersion, that.syncedContentVersion);
|
||||
std::swap(this->syncedMipLevelCount, that.syncedMipLevelCount);
|
||||
std::swap(this->syncedShapeVersion, that.syncedShapeVersion);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
std::swap(this->syncedWireSerial, that.syncedWireSerial);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
@@ -429,6 +451,9 @@ public:
|
||||
syncedContentVersion = 0;
|
||||
syncedMipLevelCount = 0;
|
||||
syncedShapeVersion = 0;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
syncedWireSerial = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
~TextureResource() {
|
||||
@@ -514,6 +539,39 @@ public:
|
||||
|
||||
TextureResource* SyncTextureAndGetDescriptor(
|
||||
MG_State::GLState::ITextureObject& texture);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5f (fm): THE HANDLE-KEYED ARM of SyncTextureAndGetDescriptor. Under an active transport
|
||||
// the apply thread may not name the client's ITextureObject (rule E), so the clear / blit /
|
||||
// readback / mipmap verbs resolve their attachment textures from the wire handle the
|
||||
// framebuffer or mip record carried: shape from the applier's resource record
|
||||
// (MGPipeApplier().TextureResources), texels from the server's staged-texture store
|
||||
// (P5c tx), pending-upload bookkeeping consumed from the same record. Returns null - never
|
||||
// a frontend fallback - when the record or the shape does not resolve; the caller declines
|
||||
// loudly, which is the honest answer a missing record deserves.
|
||||
//
|
||||
// The resource lives in m_wireTextureResources keyed by StagedTextureStore::KeyForHandle -
|
||||
// a NAMESPACE THE FRONTEND-KEYED m_textureResources NEVER MEETS, so a recycled {slot, gen}
|
||||
// can never inherit its predecessor's image. Texture views resolve the storage owner from
|
||||
// Desc.ViewOf and the window from ViewCso; buffer-backed textures remain P7's buffer arm.
|
||||
TextureResource* SyncTextureResourceByHandle(MG_Pipe::MGPipeHandle handle, Bool renderbuffer = false);
|
||||
// Map a texture/view-relative subresource to its live storage owner. layerCount, when
|
||||
// supplied, receives the number of accessible layers starting at the input layer.
|
||||
// viewFormat is UNDEFINED for an ordinary texture, or the outer view's format.
|
||||
MG_Pipe::MGPipeHandle ResolveWireTextureStorage(MG_Pipe::MGPipeHandle handle, Uint32& level,
|
||||
Uint32& layer, VkFormat* viewFormat = nullptr, Uint32* layerCount = nullptr);
|
||||
// Grows the live image of a handle-keyed resource to `requiredMipLevels`, carrying the
|
||||
// existing levels' content across with an in-command-buffer copy. The caller
|
||||
// (GenerateMipmap's record arm) has flushed every pending submission first, so the old
|
||||
// image is GPU-idle and the copy may be recorded into `commandBuffer` directly; the old
|
||||
// image goes to the deferred-release ring. False when the chain cannot grow (the resource
|
||||
// stays as it was).
|
||||
Bool GrowWireTextureMipChain(MG_Pipe::MGPipeHandle handle, Uint32 requiredMipLevels,
|
||||
VkCommandBuffer commandBuffer);
|
||||
// Records that GPU writes superseded the staged CPU snapshot of this level. Layer
|
||||
// ranges select cube faces; array and 3D levels share one staged level shadow.
|
||||
void MarkWireTextureGpuWritten(MG_Pipe::MGPipeHandle handle, Uint32 mipLevel,
|
||||
Uint32 baseArrayLayer = 0, Uint32 layerCount = 1);
|
||||
#endif
|
||||
VkImageView GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel);
|
||||
VkImageView GetOrCreateAttachmentViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
||||
Uint32 baseArrayLayer, Uint32 layerCount,
|
||||
@@ -670,6 +728,21 @@ private:
|
||||
void EraseTrackedTexture(const TextureIdentity& identity);
|
||||
void PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture);
|
||||
SizeT PruneDeadTextures();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// The two halves of SyncTextureResourceByHandle: shape from the resource record's
|
||||
// descriptor (create / recreate / compatibility), then the pending-upload walk against the
|
||||
// server's staged store. UploadPendingWireLevels mutates the applier's record (consumed
|
||||
// entries are dropped), which is why the record is a non-const reference.
|
||||
Bool SyncWireTextureShape(const MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource);
|
||||
Bool UploadPendingWireLevels(MG_Pipe::MGPipeHandle handle, MG_Pipe::MGPipeResourceRecord& record,
|
||||
TextureResource& resource);
|
||||
// The handle-keyed twin of m_textureResources, keyed by StagedTextureStore::KeyForHandle.
|
||||
// Node-based for the same reason m_renderbufferResources is: callers hold TextureResource*
|
||||
// across map-touching calls.
|
||||
std::unordered_map<Uint64, TextureResource> m_wireTextureResources;
|
||||
// Handles are per-kind; a renderbuffer and texture may have identical slot/gen.
|
||||
std::unordered_map<Uint64, TextureResource> m_wireRenderbufferResources;
|
||||
#endif
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||
|
||||
@@ -3154,9 +3154,15 @@ void main() {
|
||||
|
||||
inline ProgramFactory::CompileOptionFlags GetShaderTransformFlags(VkSurfaceTransformFlagBitsKHR preTransform) {
|
||||
ProgramFactory::CompileOptionFlags flags = ProgramFactory::CompileOptionBit::PositionZRemap;
|
||||
const auto& currentDrawFBO =
|
||||
MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||
if (currentDrawFBO != nullptr && currentDrawFBO->IsDefaultFramebuffer()) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const auto* wireFbo = MG_Config::Transport != MG_Config::TransportMode::Monolith ? MG_Pipe::MGPipeApplier().DrawFramebuffer() : nullptr;
|
||||
const Bool isDefault = wireFbo ? wireFbo->IsDefault :
|
||||
MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject()->IsDefaultFramebuffer();
|
||||
#else
|
||||
const auto& currentDrawFBO = MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||
const Bool isDefault = currentDrawFBO != nullptr && currentDrawFBO->IsDefaultFramebuffer();
|
||||
#endif
|
||||
if (isDefault) {
|
||||
flags |= ProgramFactory::CompileOptionBit::PositionYFlip;
|
||||
// gl_FragCoord follows the same rule the default-framebuffer RECTANGLES follow
|
||||
// (GetDefaultFramebufferRectMapping): flipped for identity/180, left alone under a
|
||||
@@ -3420,6 +3426,9 @@ void main() {
|
||||
if (m_device != VK_NULL_HANDLE) {
|
||||
VK_VERIFY(vkDeviceWaitIdle(m_device));
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
DestroyWireDrawPass();
|
||||
#endif
|
||||
OnSubmitsCompletedUpTo(m_submitCounter);
|
||||
DestroySubmitFencePool();
|
||||
|
||||
@@ -5210,7 +5219,7 @@ void main() {
|
||||
|
||||
// A program that runs a geometry shader AND captures transform feedback. Both halves are
|
||||
// link-time properties, so this is safe to fold into a pipeline keyed on the program hash.
|
||||
static Bool ProgramCapturesXfbFromGeometryStage(const MG_State::GLState::ProgramObject& program) {
|
||||
static Bool ProgramCapturesXfbFromGeometryStage(const MagmaProgramSource& program) {
|
||||
if (program.GetTransformFeedbackVaryingCount() == 0) return false;
|
||||
// Both halves are link-time properties, so both are asked of the LAST LINK. Reading the
|
||||
// live attach list would let a glAttachShader that has not been linked in yet - which GL
|
||||
@@ -5249,12 +5258,22 @@ void main() {
|
||||
|
||||
VkPipeline VulkanRenderer::GetOrCreatePipeline(
|
||||
GLenum mode,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
ProgramFactory::CompileOptionFlags transformFlags,
|
||||
const MG_State::GLState::VertexArrayObject& vao,
|
||||
const RenderPassEntry& renderPassEntry,
|
||||
Bool primitiveRestartEnable) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
return GetOrCreatePipelineWithInput(mode, program, programObj, transformFlags,
|
||||
m_vertexInputStateFactory->GetOrCreateVertexInputState(vao), renderPassEntry, primitiveRestartEnable);
|
||||
}
|
||||
|
||||
VkPipeline VulkanRenderer::GetOrCreatePipelineWithInput(GLenum mode, const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, ProgramFactory::CompileOptionFlags transformFlags,
|
||||
const VertexInputStateFactory::BackendVertexInputState& vis, const RenderPassEntry& renderPassEntry,
|
||||
Bool primitiveRestartEnable) {
|
||||
#endif
|
||||
Bool invertClockwise = transformFlags & ProgramFactory::CompileOptionBit::PositionYFlip;
|
||||
if (programObj.stages.empty()) {
|
||||
MGLOG_D("GetOrCreatePipeline skipped: program has no shader stages");
|
||||
@@ -5272,7 +5291,9 @@ void main() {
|
||||
// payload key on the resolved LAYOUT hash instead, so draws over identical
|
||||
// layouts share one pipeline.
|
||||
// The one-arg fetch rides the VAO's state-pointer memo (no hash, no map).
|
||||
#if !MOBILEGL_BUILD_DISAGGREGATED
|
||||
auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
|
||||
#endif
|
||||
const Uint64 vertexLayoutHash = vis.layoutHash;
|
||||
const Uint64 renderPassHash = renderPassEntry.hash;
|
||||
// The pipeline-relevant subset only: glViewport / glScissor / glBlendColor / glStencilMask
|
||||
@@ -5506,6 +5527,16 @@ void main() {
|
||||
// (stencil) test always passes and nothing is written - even when the bound
|
||||
// image is a packed depth-stencil texture attached through only one half.
|
||||
{
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer();
|
||||
if (fbo && !fbo->IsDefault) {
|
||||
if (fbo->Depth.Kind == MG_Pipe::kMGPipeSurfaceKindNone) depthTestEnabled = false;
|
||||
if (fbo->Stencil.Kind == MG_Pipe::kMGPipeSurfaceKindNone) stencilTestEnabled = false;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
const auto& gatingFbo =
|
||||
MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||
if (gatingFbo != nullptr && !gatingFbo->IsDefaultFramebuffer()) {
|
||||
@@ -5519,6 +5550,7 @@ void main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const StencilFaceState& frontStencil = MGB_CTX->GetStencilState(StencilFace::Front);
|
||||
const StencilFaceState& backStencil = MGB_CTX->GetStencilState(StencilFace::Back);
|
||||
const VkPolygonMode requestedPolygonMode =
|
||||
@@ -5726,12 +5758,31 @@ void main() {
|
||||
MOBILEGL_ASSERT(payload.colorAttachmentCount <= PipelineFactory::PipelineCreatePayload::kMaxColorAttachments,
|
||||
"GetOrCreatePipeline: colorAttachmentCount=%u exceeds payload capacity",
|
||||
payload.colorAttachmentCount);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const auto* wireFbo = MG_Config::Transport != MG_Config::TransportMode::Monolith ?
|
||||
MG_Pipe::MGPipeApplier().DrawFramebuffer() : nullptr;
|
||||
const auto drawFboBinding = wireFbo ? SharedPtr<MG_State::GLState::FramebufferObject>{} :
|
||||
MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||
#else
|
||||
const auto& drawFboBinding =
|
||||
MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||
#endif
|
||||
MOBILEGL_ASSERT(drawFboBinding != nullptr, "GetOrCreatePipeline: draw framebuffer is null");
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const Bool isDefaultDrawFbo = wireFbo ? wireFbo->IsDefault : drawFboBinding->IsDefaultFramebuffer();
|
||||
Array<FramebufferAttachmentType, MG_Pipe::kMGPipeMaxColorAttachments> wireDrawBuffers{};
|
||||
if (wireFbo) for (SizeT i = 0; i < wireDrawBuffers.size(); ++i)
|
||||
wireDrawBuffers[i] = wireFbo->DrawBuffers[i] < 0 ? FramebufferAttachmentType::None :
|
||||
static_cast<FramebufferAttachmentType>(static_cast<Int>(FramebufferAttachmentType::Color0) + wireFbo->DrawBuffers[i]);
|
||||
const auto& drawBuffers = wireFbo ? wireDrawBuffers : drawFboBinding->GetDrawBuffers();
|
||||
#else
|
||||
const Bool isDefaultDrawFbo = drawFboBinding->IsDefaultFramebuffer();
|
||||
const auto& drawBuffers = drawFboBinding->GetDrawBuffers();
|
||||
#endif
|
||||
auto resolveCompleteColorAttachmentTexture = [&](Uint32 drawBufferIndex) -> MG_State::GLState::ITextureObject* {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (wireFbo) return nullptr;
|
||||
#endif
|
||||
if (isDefaultDrawFbo || drawBufferIndex >= drawBuffers.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -5782,6 +5833,15 @@ void main() {
|
||||
attachmentColorWriteMask = 0;
|
||||
effectiveBlendEnabled = false;
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (wireFbo && !isDefaultDrawFbo) {
|
||||
const Int32 slot = wireFbo->DrawBuffers[i];
|
||||
if (slot < 0 || wireFbo->Color[slot].Kind == MG_Pipe::kMGPipeSurfaceKindNone) {
|
||||
attachmentColorWriteMask = 0;
|
||||
effectiveBlendEnabled = false;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if (!isDefaultDrawFbo && i < drawBuffers.size()) {
|
||||
const auto drawBuffer = drawBuffers[i];
|
||||
colorAttachmentTexture = resolveCompleteColorAttachmentTexture(i);
|
||||
@@ -5877,6 +5937,12 @@ void main() {
|
||||
|
||||
VkFormat colorAttachmentFormat = VK_FORMAT_UNDEFINED;
|
||||
Int textureExternalIndex = -1;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (wireFbo && !isDefaultDrawFbo) {
|
||||
const Int32 slot = wireFbo->DrawBuffers[i];
|
||||
colorAttachmentFormat = ResolveWireImage(*wireFbo, wireFbo->Color[slot], VK_IMAGE_ASPECT_COLOR_BIT).format;
|
||||
} else
|
||||
#endif
|
||||
if (isDefaultDrawFbo) {
|
||||
colorAttachmentFormat = m_swapchainObject.GetSurfaceFormat().format;
|
||||
} else if (colorAttachmentRenderbuffer != nullptr) {
|
||||
@@ -6004,7 +6070,7 @@ void main() {
|
||||
|
||||
Bool VulkanRenderer::PrepareStorageImageTextures(
|
||||
FrameContext::FrameData& frame,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj) {
|
||||
if (!programObj.hasStorageImages) {
|
||||
return true;
|
||||
@@ -6100,7 +6166,7 @@ void main() {
|
||||
}
|
||||
Bool VulkanRenderer::PrepareSamplerImageFeedbackSnapshots(
|
||||
FrameContext::FrameData& frame,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
VkPipelineStageFlags consumerShaderStageMask) {
|
||||
auto& feedbackBindings = m_samplerImageFeedbackScratch;
|
||||
@@ -6801,6 +6867,10 @@ void main() {
|
||||
Bool VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
||||
const DrawCmdParam& drawParams,
|
||||
const IndexBufferView* pIndexBufferView) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith)
|
||||
return SetupWireDraw(frame, mode, aspects, drawParams);
|
||||
#endif
|
||||
// Sync each sampled texture at most once across this whole draw: the layout
|
||||
// probe loop, the post-transition loop, and ResolveSamplerDescriptor would
|
||||
// otherwise each re-run the full SyncTexture path on the same textures.
|
||||
@@ -7417,6 +7487,12 @@ void main() {
|
||||
}
|
||||
|
||||
void VulkanRenderer::DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
DispatchWireCompute(numGroupsX, numGroupsY, numGroupsZ);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
m_textureManager->CollectGarbage();
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
// The DISPATCH accessor: with a pipeline bound this is its compute stage program
|
||||
@@ -7636,7 +7712,23 @@ void main() {
|
||||
return ScissoredClearPrep::Ready;
|
||||
}
|
||||
|
||||
#include "WireFramebuffer.inc"
|
||||
#include "WireDraw.inc"
|
||||
|
||||
void VulkanRenderer::Clear(GLbitfield mask) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer();
|
||||
if (!fbo) MagmaWireFatal("clear-framebuffer-record");
|
||||
ClearAttachmentPayload payload{};
|
||||
payload.mask = mask;
|
||||
payload.color = MGB_CTX->GetClearColor();
|
||||
payload.depth = MGB_CTX->GetClearDepth();
|
||||
payload.stencil = MGB_CTX->GetClearStencil();
|
||||
ClearWireFramebuffer(*fbo, payload);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
m_clearManager->CollectGarbage();
|
||||
if ((mask & (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) == 0) {
|
||||
return;
|
||||
@@ -8013,6 +8105,14 @@ void main() {
|
||||
|
||||
void VulkanRenderer::QueueClearBufferPayload(GLenum buffer, GLint drawbuffer,
|
||||
const ClearAttachmentPayload& clearPayload) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer();
|
||||
if (!fbo) MagmaWireFatal("clear-buffer-framebuffer-record");
|
||||
ClearWireFramebuffer(*fbo,clearPayload,buffer == GL_COLOR ? drawbuffer : -1);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
auto* fbo = MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject().get();
|
||||
if (!fbo) {
|
||||
return;
|
||||
@@ -8971,55 +9071,42 @@ void main() {
|
||||
void VulkanRenderer::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (G6, CONTRACT-P5C §3.3/§5.4): MAGMA'S NAMED ARM. A blit record whose
|
||||
// ReadFbo/DrawFbo are non-null is a glBlitNamedFramebuffer: both framebuffers resolve
|
||||
// from the record's handles, and the sink is told the pair was consumed - a backend
|
||||
// that leaves the flag clear has no named arm and the verb declines there, loudly.
|
||||
// Magma has no FBO twin registry (it reads the frontend object wherever it syncs), so
|
||||
// the resolution here is frontend-keyed - the handle was minted over the frontend
|
||||
// object's lifetime id, and the two probes below (the client allocator's slot entry
|
||||
// and the frontend context's framebuffer pool) are named debt inside the scope, the
|
||||
// same shape as Espryt's StateForHandle arm: P7 retires it by carrying the object
|
||||
// identity in the record. P5e (id), ruling 12: the third of Magma's four debts, so the
|
||||
// scope it rides in is MagmaP7AllocatorDebtScope. Magma stays lockstep for the whole of
|
||||
// P5e (CONTRACT-P5E §6.1), so the record being applied here is always barriered and the
|
||||
// exemption still holds.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
const MG_Pipe::MGPipeHandle readHandle = applierState.VerbBlitReadFbo;
|
||||
const MG_Pipe::MGPipeHandle drawHandle = applierState.VerbBlitDrawFbo;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(readHandle) || !MG_Pipe::MGPipeHandleIsNull(drawHandle)) {
|
||||
const auto resolveEndpoint = [](MG_Pipe::MGPipeHandle handle)
|
||||
-> SharedPtr<MG_State::GLState::FramebufferObject> {
|
||||
const MG_Pipe::MagmaP7AllocatorDebtScope magmaP7AllocatorDebt;
|
||||
if (handle == MG_Pipe::kMGPipeDefaultFramebuffer) {
|
||||
return MG_State::pGLContext ? MG_State::pGLContext->GetFramebufferObject(0) : nullptr;
|
||||
}
|
||||
if (!MG_Pipe::MGPipeSlots().IsLive(MG_Pipe::MGPipeKind::Framebuffer, handle)) {
|
||||
return nullptr;
|
||||
}
|
||||
const Uint64 lifetimeId =
|
||||
MG_Pipe::MGPipeSlots().LifetimeIdOfSlot(MG_Pipe::MGPipeKind::Framebuffer, handle.Slot);
|
||||
if (lifetimeId == 0 || MG_State::pGLContext == nullptr) return nullptr;
|
||||
return MG_State::pGLContext->FindFramebufferObjectByLifetimeId(lifetimeId);
|
||||
};
|
||||
auto readFbo = resolveEndpoint(readHandle);
|
||||
auto drawFbo = resolveEndpoint(drawHandle);
|
||||
if (!readFbo || !drawFbo) {
|
||||
// Leave the pair UNCONSUMED: the sink's decline is the loud answer a
|
||||
// missing endpoint deserves, not a silent blit of whatever is bound.
|
||||
MGLOG_E_ONCE("MGPipe: Magma's named blit could not resolve an endpoint (read {%u, %u}, draw "
|
||||
"{%u, %u}) to a frontend framebuffer; the verb declines at the sink",
|
||||
readHandle.Slot, readHandle.Gen, drawHandle.Slot, drawHandle.Gen);
|
||||
return;
|
||||
// The scissor test clips blit writes: intersect the destination rectangle with
|
||||
// the scissor box and shrink the source proportionally.
|
||||
if (MGB_CTX->IsCapabilityEnabled(CapabilityInput::ScissorTest)) {
|
||||
const IntVec4& scissor = MGB_CTX->GetScissorBox();
|
||||
const auto clipAxis = [](GLint& d0, GLint& d1, GLint& s0, GLint& s1, GLint clipLo, GLint clipHi) -> Bool {
|
||||
const Bool dstFlipped = d1 < d0;
|
||||
GLint lo = dstFlipped ? d1 : d0;
|
||||
GLint hi = dstFlipped ? d0 : d1;
|
||||
const GLint newLo = std::max(lo, clipLo);
|
||||
const GLint newHi = std::min(hi, clipHi);
|
||||
if (newLo >= newHi) {
|
||||
return false;
|
||||
}
|
||||
applierState.VerbBlitNamedConsumed = true;
|
||||
BlitNamedFramebuffer(readFbo, drawFbo, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0,
|
||||
dstX1, dstY1, mask, filter);
|
||||
return;
|
||||
const double srcSpan = static_cast<double>(s1 - s0);
|
||||
const double dstSpan = static_cast<double>(d1 - d0);
|
||||
const double scale = dstSpan != 0.0 ? srcSpan / dstSpan : 0.0;
|
||||
const GLint origD0 = d0;
|
||||
const GLint clippedD0 = dstFlipped ? newHi : newLo;
|
||||
const GLint clippedD1 = dstFlipped ? newLo : newHi;
|
||||
s0 = s0 + static_cast<GLint>(std::lround((clippedD0 - origD0) * scale));
|
||||
s1 = s0 + static_cast<GLint>(std::lround((clippedD1 - clippedD0) * scale));
|
||||
d0 = clippedD0;
|
||||
d1 = clippedD1;
|
||||
return true;
|
||||
};
|
||||
if (!clipAxis(dstX0, dstX1, srcX0, srcX1, scissor.x(), scissor.x() + scissor.z()) ||
|
||||
!clipAxis(dstY0, dstY1, srcY0, srcY1, scissor.y(), scissor.y() + scissor.w())) {
|
||||
return; // fully scissored out
|
||||
}
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
BlitWireFramebuffers(srcX0,srcY0,srcX1,srcY1,dstX0,dstY0,dstX1,dstY1,mask,filter);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
auto readFbo = MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
|
||||
auto drawFbo = MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||
@@ -9052,37 +9139,6 @@ void main() {
|
||||
return;
|
||||
}
|
||||
|
||||
// The scissor test clips blit writes: intersect the destination rectangle with
|
||||
// the scissor box and shrink the source proportionally.
|
||||
if (MGB_CTX->IsCapabilityEnabled(CapabilityInput::ScissorTest)) {
|
||||
const IntVec4& scissor = MGB_CTX->GetScissorBox();
|
||||
const auto clipAxis = [](GLint& d0, GLint& d1, GLint& s0, GLint& s1, GLint clipLo, GLint clipHi) -> Bool {
|
||||
const Bool dstFlipped = d1 < d0;
|
||||
GLint lo = dstFlipped ? d1 : d0;
|
||||
GLint hi = dstFlipped ? d0 : d1;
|
||||
const GLint newLo = std::max(lo, clipLo);
|
||||
const GLint newHi = std::min(hi, clipHi);
|
||||
if (newLo >= newHi) {
|
||||
return false;
|
||||
}
|
||||
const double srcSpan = static_cast<double>(s1 - s0);
|
||||
const double dstSpan = static_cast<double>(d1 - d0);
|
||||
const double scale = dstSpan != 0.0 ? srcSpan / dstSpan : 0.0;
|
||||
const GLint origD0 = d0;
|
||||
const GLint clippedD0 = dstFlipped ? newHi : newLo;
|
||||
const GLint clippedD1 = dstFlipped ? newLo : newHi;
|
||||
s0 = s0 + static_cast<GLint>(std::lround((clippedD0 - origD0) * scale));
|
||||
s1 = s0 + static_cast<GLint>(std::lround((clippedD1 - clippedD0) * scale));
|
||||
d0 = clippedD0;
|
||||
d1 = clippedD1;
|
||||
return true;
|
||||
};
|
||||
if (!clipAxis(dstX0, dstX1, srcX0, srcX1, scissor.x(), scissor.x() + scissor.z()) ||
|
||||
!clipAxis(dstY0, dstY1, srcY0, srcY1, scissor.y(), scissor.y() + scissor.w())) {
|
||||
return; // fully scissored out
|
||||
}
|
||||
}
|
||||
|
||||
MOBILEGL_ASSERT(readFbo != nullptr, "VulkanRenderer::BlitFramebuffer: read framebuffer is null");
|
||||
MOBILEGL_ASSERT(drawFbo != nullptr, "VulkanRenderer::BlitFramebuffer: draw framebuffer is null");
|
||||
if (IsUnsupportedFramebufferForDirectVulkan(*readFbo) ||
|
||||
@@ -9633,6 +9689,12 @@ void main() {
|
||||
|
||||
void VulkanRenderer::CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||
GLint x, GLint y, GLsizei width, GLsizei height) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
CopyWireFramebufferToTexture(target,level,xoffset,yoffset,x,y,width,height);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -9952,6 +10014,10 @@ void main() {
|
||||
const CopyImageEndpoint& dstEndpoint,
|
||||
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const Bool wire = MG_Config::Transport != MG_Config::TransportMode::Monolith;
|
||||
if (wire && HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("copy-image-flush");
|
||||
#endif
|
||||
MOBILEGL_ASSERT(srcEndpoint.Exists() && dstEndpoint.Exists(),
|
||||
"CopyImageSubData requires valid source and destination images.");
|
||||
// The frontend already declines a zero or negative extent, so anything else here is a
|
||||
@@ -9978,7 +10044,11 @@ void main() {
|
||||
srcEndpoint.Texture ? &VkTextureManager::StorageTextureOf(*srcEndpoint.Texture) : nullptr;
|
||||
const auto* dstStorageTexture =
|
||||
dstEndpoint.Texture ? &VkTextureManager::StorageTextureOf(*dstEndpoint.Texture) : nullptr;
|
||||
if (srcStorageTexture == dstStorageTexture && srcEndpoint.Renderbuffer == dstEndpoint.Renderbuffer) {
|
||||
if (
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
!wire &&
|
||||
#endif
|
||||
srcStorageTexture == dstStorageTexture && srcEndpoint.Renderbuffer == dstEndpoint.Renderbuffer) {
|
||||
MGLOG_E_ONCE("%s: in-place copy on objectId=%u is not supported; declining the copy", __func__,
|
||||
CopyImageEndpointName(srcEndpoint));
|
||||
return;
|
||||
@@ -9997,6 +10067,21 @@ void main() {
|
||||
// SyncTextureAndGetDescriptor the copy always used; the renderbuffer arm goes through the
|
||||
// render-pass manager, which is where a renderbuffer's VkImage lives.
|
||||
const auto resolveImage = [this](const CopyImageEndpoint& endpoint, CopyImageVkImage& out) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
auto* resource = m_textureManager->SyncTextureResourceByHandle(endpoint.TextureHandle);
|
||||
if (!resource) MagmaWireFatal("copy-image-resource");
|
||||
out.image = resource->image;
|
||||
out.trackedLayout = &resource->layout;
|
||||
out.aspect = resource->aspect;
|
||||
out.mipLevels = resource->mipLevels;
|
||||
out.extent = resource->extent;
|
||||
out.depth = resource->depth;
|
||||
out.arrayLayers = resource->arrayLayers;
|
||||
out.format = resource->format;
|
||||
return out.image != VK_NULL_HANDLE;
|
||||
}
|
||||
#endif
|
||||
if (endpoint.IsRenderbuffer()) {
|
||||
auto* resource = m_renderPassManager->GetOrCreateRenderbufferResource(endpoint.Renderbuffer);
|
||||
if (resource == nullptr) return false;
|
||||
@@ -10031,6 +10116,12 @@ void main() {
|
||||
CopyImageVkImage dstImage{};
|
||||
const Bool srcResolved = resolveImage(srcEndpoint, srcImage);
|
||||
const Bool dstResolved = resolveImage(dstEndpoint, dstImage);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (wire) {
|
||||
if (srcImage.image == dstImage.image) MagmaWireFatal("copy-image-in-place@P7");
|
||||
m_textureManager->FlushPendingUploads();
|
||||
}
|
||||
#endif
|
||||
// Real checks, not MOBILEGL_ASSERT: the assertions this replaces compile to nothing in
|
||||
// a release build, which is where both observed failures happened - a null resource
|
||||
// dereferenced right below (lavapipe) and a mip level the VkImage does not have handed
|
||||
@@ -10159,6 +10250,9 @@ void main() {
|
||||
}
|
||||
|
||||
const auto materializeClear = [this, &frame](const CopyImageEndpoint& endpoint) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return true;
|
||||
#endif
|
||||
if (endpoint.IsRenderbuffer()) {
|
||||
return MaterializePendingClearForRenderbuffer(frame.commandBuffer, endpoint.Renderbuffer);
|
||||
}
|
||||
@@ -10270,6 +10364,10 @@ void main() {
|
||||
srcImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
dstImage.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1, ©Region);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (wire) m_textureManager->MarkWireTextureGpuWritten(dstEndpoint.TextureHandle,
|
||||
dstMipLevel, dstSlices.BaseArrayLayer(), dstSlices.slicesAreDepth ? 1u : copySliceCount);
|
||||
#endif
|
||||
|
||||
VkPipelineStageFlags srcRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags srcRestoreAccessMask = 0;
|
||||
@@ -10364,6 +10462,12 @@ void main() {
|
||||
|
||||
void VulkanRenderer::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||
void* pixels) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
ReadWirePixels(x,y,width,height,format,type,pixels);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -11376,6 +11480,12 @@ void main() {
|
||||
}
|
||||
|
||||
void VulkanRenderer::GenerateMipmap(GLenum target) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
GenerateWireMipmap();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||
// Whatever is left here is a coverage gap in this backend, not a broken invariant, so it
|
||||
// declines (leaving the mip chain unwritten) rather than asserting the process down. What
|
||||
|
||||
@@ -433,6 +433,44 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
};
|
||||
|
||||
void QueueClearBufferPayload(GLenum buffer, GLint drawbuffer, const ClearAttachmentPayload& clearPayload);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
struct WireImage {
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent{};
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkImageLayout* trackedLayout = nullptr;
|
||||
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkImageAspectFlags aspect = 0;
|
||||
Uint32 level = 0, layer = 0, layers = 1, levels = 1;
|
||||
Bool isDefault = false;
|
||||
Bool is3D = false;
|
||||
MG_Pipe::MGPipeHandle storage = MG_Pipe::kMGPipeNullHandle;
|
||||
};
|
||||
WireImage ResolveWireImage(const MG_Pipe::MGPFramebufferState& fbo,
|
||||
const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect);
|
||||
void TransitionWireImage(WireImage& image, VkImageLayout layout);
|
||||
void ClearWireFramebuffer(const MG_Pipe::MGPFramebufferState& fbo,
|
||||
const ClearAttachmentPayload& payload, GLint drawbuffer = -1);
|
||||
void BlitWireFramebuffers(GLint sx0, GLint sy0, GLint sx1, GLint sy1,
|
||||
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLbitfield mask, GLenum filter);
|
||||
void ReadWirePixels(GLint x, GLint y, GLsizei width, GLsizei height,
|
||||
GLenum format, GLenum type, void* pixels);
|
||||
void GenerateWireMipmap();
|
||||
Bool SetupWireDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
||||
const DrawCmdParam& drawParams);
|
||||
void DestroyWireDrawPass();
|
||||
void DispatchWireCompute(GLuint x, GLuint y, GLuint z);
|
||||
UniquePtr<RenderPassEntry> m_wireDrawPass;
|
||||
Vector<VkImageView> m_wireDrawViews;
|
||||
VkPipeline GetOrCreatePipelineWithInput(GLenum mode, const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, ProgramFactory::CompileOptionFlags transformFlags,
|
||||
const VertexInputStateFactory::BackendVertexInputState& vis, const RenderPassEntry& renderPassEntry,
|
||||
Bool primitiveRestartEnable);
|
||||
|
||||
void CopyWireFramebufferToTexture(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||
GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
#endif
|
||||
void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer,
|
||||
const ClearAttachmentPayload& clearPayload);
|
||||
@@ -1508,7 +1546,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
VkPipeline GetOrCreatePipeline(
|
||||
GLenum mode,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
ProgramFactory::CompileOptionFlags transformFlags,
|
||||
const MG_State::GLState::VertexArrayObject& vao,
|
||||
@@ -1520,14 +1558,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// flush the pending recording (see the body), which retires the current command buffer.
|
||||
Bool PrepareStorageImageTextures(
|
||||
FrameContext::FrameData& frame,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj);
|
||||
// Vulkan forbids a sampled descriptor and writable storage descriptor from naming the
|
||||
// same image subresource in one shader operation. Snapshot only the sampler side; the
|
||||
// storage descriptor continues to name the application texture.
|
||||
Bool PrepareSamplerImageFeedbackSnapshots(
|
||||
FrameContext::FrameData& frame,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
VkPipelineStageFlags consumerShaderStageMask);
|
||||
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
// Server-owned draw/dispatch setup. The legacy setup remains the monolith implementation.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
static MagmaProgramSource WireProgramSource(MG_Pipe::MGPipeHandle handle) {
|
||||
auto& state = MG_Pipe::MGPipeApplier();
|
||||
const Bool composite = handle.Slot >= MG_Pipe::kMGPipeShaderCsoCompositeSlotBase;
|
||||
const auto& records = composite ? state.CompositeShaderCsos : state.ShaderCsos;
|
||||
const SizeT index = composite ? handle.Slot - MG_Pipe::kMGPipeShaderCsoCompositeSlotBase : handle.Slot;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle) || index >= records.size() ||
|
||||
!records[index].Live || records[index].Gen != handle.Gen || !records[index].Archive)
|
||||
MagmaWireFatal("program-record");
|
||||
return MagmaProgramSource(handle, records[index]);
|
||||
}
|
||||
|
||||
[[noreturn]] static void WireBufferLegacyFatal() {
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} (Magma P7 buffer consumer)");
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void VulkanRenderer::DestroyWireDrawPass() {
|
||||
m_wireDrawPass.reset();
|
||||
for (const auto view : m_wireDrawViews) vkDestroyImageView(m_device, view, nullptr);
|
||||
m_wireDrawViews.clear();
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::SetupWireDraw(FrameContext::FrameData& frame, GLenum mode,
|
||||
Flags<DrawSetupAspect> aspects, const DrawCmdParam& drawParams) {
|
||||
auto& state = MG_Pipe::MGPipeApplier();
|
||||
const auto program = WireProgramSource(state.BoundShaderCso);
|
||||
if (!program.GetLinkStatus() || !program.GetSpirvStatus()) return false;
|
||||
if (aspects & DrawSetupAspect::IndexBuffer || aspects & DrawSetupAspect::IndirectDrawBuffer ||
|
||||
MGB_CTX->IsTransformFeedbackActive()) WireBufferLegacyFatal();
|
||||
const auto* fbo = state.DrawFramebuffer();
|
||||
if (!fbo) MagmaWireFatal("draw-framebuffer-record");
|
||||
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("draw-flush");
|
||||
DestroyWireDrawPass();
|
||||
|
||||
ProgramFactory::CompileOptionFlags flags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
|
||||
const auto* programObject = &m_programFactory->GetOrCreateProgram(program, flags);
|
||||
if (programObject->readsBaseVertexBuiltin) {
|
||||
flags |= ProgramFactory::CompileOptionBit::ZeroBaseVertex;
|
||||
programObject = &m_programFactory->GetOrCreateProgram(program, flags);
|
||||
}
|
||||
if (programObject->pointSizeCapabilityUnsupported) return false;
|
||||
const auto& programObj = *programObject;
|
||||
const auto vaoHandle = state.BoundVertexElements;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(vaoHandle)) {
|
||||
if (vaoHandle.Slot >= state.VertexElementsCsos.size()) MagmaWireFatal("vertex-elements-record");
|
||||
const auto& vao = state.VertexElementsCsos[vaoHandle.Slot];
|
||||
if (!vao.Live || vao.Gen != vaoHandle.Gen) MagmaWireFatal("vertex-elements-generation");
|
||||
for (Uint32 i = 0; i < vao.AttributeCount; ++i)
|
||||
if (vao.Attributes[i].Enabled && (programObj.activeVertexInputLocationMask & (1u << i)))
|
||||
WireBufferLegacyFatal();
|
||||
}
|
||||
|
||||
Vector<VkAttachmentDescription> attachments;
|
||||
Vector<VkImageView> views;
|
||||
Vector<VkAttachmentReference> colors;
|
||||
VkAttachmentReference depth{VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
||||
VkExtent2D extent{~0u, ~0u};
|
||||
Uint32 layers = ~0u;
|
||||
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
Uint64 passHash = 0;
|
||||
const auto add = [&](const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect) -> Uint32 {
|
||||
WireImage image = ResolveWireImage(*fbo, surface, aspect);
|
||||
if (!image.image) return VK_ATTACHMENT_UNUSED;
|
||||
m_textureManager->FlushPendingUploads();
|
||||
const VkImageLayout layout = aspect == VK_IMAGE_ASPECT_COLOR_BIT ?
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
TransitionWireImage(image, layout);
|
||||
VkImageViewCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
info.image = image.image;
|
||||
info.viewType = image.layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
|
||||
info.format = image.format;
|
||||
info.subresourceRange = {aspect, image.level, 1, image.layer, image.layers};
|
||||
if (image.format == VK_FORMAT_D24_UNORM_S8_UINT || image.format == VK_FORMAT_D32_SFLOAT_S8_UINT)
|
||||
info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateImageView(m_device, &info, nullptr, &view));
|
||||
m_wireDrawViews.push_back(view);
|
||||
views.push_back(view);
|
||||
VkAttachmentDescription description{};
|
||||
description.format = image.format;
|
||||
description.samples = image.samples;
|
||||
description.loadOp = description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
description.storeOp = description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
description.initialLayout = description.finalLayout = layout;
|
||||
attachments.push_back(description);
|
||||
extent.width = std::min(extent.width, image.extent.width);
|
||||
extent.height = std::min(extent.height, image.extent.height);
|
||||
layers = std::min(layers, image.layers);
|
||||
samples = image.samples;
|
||||
passHash = XXH64(&description, sizeof(description), passHash);
|
||||
if (!image.isDefault && surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture)
|
||||
m_textureManager->MarkWireTextureGpuWritten(image.storage, image.level, image.layer, image.layers);
|
||||
if (image.isDefault) {
|
||||
if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) m_swapchainObject.SetImageContentDefined(m_imageIndexAcquired, true);
|
||||
else m_swapchainObject.SetDepthStencilContentDefined(m_imageIndexAcquired, true);
|
||||
}
|
||||
return static_cast<Uint32>(attachments.size() - 1);
|
||||
};
|
||||
for (Uint32 i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i) {
|
||||
const Int32 slot = fbo->DrawBuffers[i];
|
||||
const Uint32 index = slot < 0 ? VK_ATTACHMENT_UNUSED : add(fbo->Color[slot], VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
colors.push_back({index, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL});
|
||||
}
|
||||
while (!colors.empty() && colors.back().attachment == VK_ATTACHMENT_UNUSED) colors.pop_back();
|
||||
if (fbo->IsDefault || fbo->Depth.Kind != MG_Pipe::kMGPipeSurfaceKindNone)
|
||||
depth.attachment = add(fbo->Depth, VK_IMAGE_ASPECT_DEPTH_BIT);
|
||||
else if (fbo->Stencil.Kind != MG_Pipe::kMGPipeSurfaceKindNone)
|
||||
depth.attachment = add(fbo->Stencil, VK_IMAGE_ASPECT_STENCIL_BIT);
|
||||
if (attachments.empty()) return false;
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = static_cast<Uint32>(colors.size());
|
||||
subpass.pColorAttachments = colors.data();
|
||||
subpass.pDepthStencilAttachment = depth.attachment != VK_ATTACHMENT_UNUSED ? &depth : nullptr;
|
||||
VkRenderPassCreateInfo passInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||||
passInfo.attachmentCount = static_cast<Uint32>(attachments.size());
|
||||
passInfo.pAttachments = attachments.data();
|
||||
passInfo.subpassCount = 1;
|
||||
passInfo.pSubpasses = &subpass;
|
||||
m_wireDrawPass = MakeUnique<RenderPassEntry>();
|
||||
auto& pass = *m_wireDrawPass;
|
||||
VK_VERIFY(vkCreateRenderPass(m_device, &passInfo, nullptr, &pass.renderPass));
|
||||
VkFramebufferCreateInfo fbInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
fbInfo.renderPass = pass.renderPass;
|
||||
fbInfo.attachmentCount = static_cast<Uint32>(views.size());
|
||||
fbInfo.pAttachments = views.data();
|
||||
fbInfo.width = extent.width; fbInfo.height = extent.height; fbInfo.layers = layers;
|
||||
VK_VERIFY(vkCreateFramebuffer(m_device, &fbInfo, nullptr, &pass.framebuffer));
|
||||
pass.attachmentCount = static_cast<Uint32>(attachments.size());
|
||||
pass.colorAttachmentCount = static_cast<Uint32>(colors.size());
|
||||
pass.hasDepthStencilAttachment = depth.attachment != VK_ATTACHMENT_UNUSED;
|
||||
pass.sampleCount = samples;
|
||||
pass.extent = {static_cast<Int>(extent.width), static_cast<Int>(extent.height)};
|
||||
pass.layers = layers;
|
||||
pass.hash = pass.compatibilityHash = XXH64(colors.data(), colors.size() * sizeof(colors[0]), passHash);
|
||||
VertexInputStateFactory::BackendVertexInputState input{};
|
||||
const auto pipeline = GetOrCreatePipelineWithInput(mode, program, programObj, flags, input, pass, false);
|
||||
if (pipeline == VK_NULL_HANDLE) return false;
|
||||
if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording();
|
||||
if (!m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj,
|
||||
m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_GRAPHICS)) return false;
|
||||
Uint32 binding = 0;
|
||||
for (Uint32 location = 0; location < kMaxVertexAttribs; ++location) {
|
||||
if (!(programObj.activeVertexInputLocationMask & (1u << location))) continue;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
const void* data = nullptr;
|
||||
VkDeviceSize size = 0;
|
||||
if (!TryGetCurrentVertexAttributeUploadPayload(MGB_CTX->GetCurrentVertexAttribute(location),
|
||||
programObj.vertexInputTypes[location], format, data, size)) return false;
|
||||
BufferSlice slice{};
|
||||
if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(),
|
||||
data, size, 16, slice)) return false;
|
||||
vkCmdBindVertexBuffers(frame.commandBuffer, binding++, 1, &slice.buffer, &slice.offset);
|
||||
}
|
||||
if (!VkRenderPassManager::BeginRenderPass(frame.commandBuffer, pass)) return false;
|
||||
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
ApplyDynamicDrawStateTail(frame, pass.extent, fbo->IsDefault,
|
||||
ResolveDrawViewportCount(programObj.writesViewportIndexBuiltin));
|
||||
(void)drawParams;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanRenderer::DispatchWireCompute(GLuint x, GLuint y, GLuint z) {
|
||||
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("dispatch-flush");
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
const auto program = WireProgramSource(MG_Pipe::MGPipeApplier().DispatchProgram);
|
||||
if (!program.GetLinkStatus() || !program.GetSpirvStatus()) return;
|
||||
const auto& programObj = m_programFactory->GetOrCreateProgram(program, {});
|
||||
const auto pipeline = GetOrCreateComputePipeline(programObj);
|
||||
if (!pipeline) return;
|
||||
if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording();
|
||||
if (!m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj,
|
||||
m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_COMPUTE)) return;
|
||||
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
|
||||
vkCmdDispatch(frame.commandBuffer, x, y, z);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,426 @@
|
||||
// P5f fm: included by VulkanRenderer.cpp inside the backend namespace. All inputs here
|
||||
// belong to the applier; no frontend object, lifetime-id lookup or allocator is used.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
[[noreturn]] static void MagmaWireFatal(const char* detail) {
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"Magma:%s\"}", detail);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
VulkanRenderer::WireImage VulkanRenderer::ResolveWireImage(
|
||||
const MG_Pipe::MGPFramebufferState& fbo, const MG_Pipe::MGPSurface& surface,
|
||||
VkImageAspectFlags aspect) {
|
||||
WireImage out{};
|
||||
out.aspect = aspect;
|
||||
if (fbo.IsDefault) {
|
||||
out.isDefault = true;
|
||||
out.extent = m_swapchainObject.GetExtent();
|
||||
if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) {
|
||||
out.image = m_swapchainObject.GetImage(m_imageIndexAcquired);
|
||||
out.format = m_swapchainObject.GetSurfaceFormat().format;
|
||||
out.layout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired);
|
||||
} else {
|
||||
out.image = m_swapchainObject.GetDepthStencilImage(m_imageIndexAcquired);
|
||||
out.format = m_swapchainObject.GetDepthStencilFormat();
|
||||
out.layout = m_swapchainObject.GetDepthStencilImageLayout(m_imageIndexAcquired);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (surface.Kind == MG_Pipe::kMGPipeSurfaceKindNone) return out;
|
||||
const Bool renderbuffer = surface.Kind == MG_Pipe::kMGPipeSurfaceKindRenderbuffer;
|
||||
Uint32 level = surface.Level, layer = surface.Layer, availableLayers = 1;
|
||||
VkFormat viewFormat = VK_FORMAT_UNDEFINED;
|
||||
out.storage = renderbuffer ? surface.Res : m_textureManager->ResolveWireTextureStorage(
|
||||
surface.Res, level, layer, &viewFormat, &availableLayers);
|
||||
if (MG_Pipe::MGPipeHandleIsNull(out.storage)) MagmaWireFatal("framebuffer-view-record");
|
||||
auto* resource = m_textureManager->SyncTextureResourceByHandle(out.storage, renderbuffer);
|
||||
if (!resource) MagmaWireFatal("framebuffer-resource");
|
||||
out.image = resource->image;
|
||||
out.format = viewFormat != VK_FORMAT_UNDEFINED ? viewFormat : resource->format;
|
||||
out.extent = {std::max(resource->extent.width >> level, 1u),
|
||||
std::max(resource->extent.height >> level, 1u)};
|
||||
out.layout = resource->layout;
|
||||
out.trackedLayout = &resource->layout;
|
||||
out.samples = resource->sampleCount;
|
||||
out.level = level;
|
||||
out.levels = resource->mipLevels;
|
||||
out.layer = layer;
|
||||
out.is3D = resource->depth > 1;
|
||||
out.layers = surface.Layered ? (out.is3D ? std::max(resource->depth >> level, 1u) : availableLayers) : 1;
|
||||
if (out.level >= resource->mipLevels || out.layer + out.layers >
|
||||
(out.is3D ? std::max(resource->depth >> level, 1u) : resource->arrayLayers))
|
||||
MagmaWireFatal("framebuffer-subresource");
|
||||
return out;
|
||||
}
|
||||
|
||||
void VulkanRenderer::TransitionWireImage(WireImage& image, VkImageLayout layout) {
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording();
|
||||
if (VkRenderPassManager::GetActiveRenderPass()) VkRenderPassManager::EndRenderPass(frame.commandBuffer);
|
||||
VkPipelineStageFlags stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags access = 0;
|
||||
GetImageTransitionSourceState(image.layout, stage, access);
|
||||
// One tracked layout per image: move the complete mip chain, never just the surface.
|
||||
VkImageAspectFlags barrierAspect = image.aspect;
|
||||
if (image.format == VK_FORMAT_D24_UNORM_S8_UINT || image.format == VK_FORMAT_D32_SFLOAT_S8_UINT)
|
||||
barrierAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
if (!VkTextureManager::TransitionImageLayout(frame.commandBuffer, image.image, image.layout, layout,
|
||||
stage, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, access,
|
||||
VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, barrierAspect, 0, image.levels))
|
||||
MagmaWireFatal("image-transition");
|
||||
if (image.trackedLayout) *image.trackedLayout = image.layout;
|
||||
if (image.isDefault) {
|
||||
if (image.aspect == VK_IMAGE_ASPECT_COLOR_BIT)
|
||||
m_swapchainObject.SetImageLayout(m_imageIndexAcquired, image.layout);
|
||||
else m_swapchainObject.SetDepthStencilImageLayout(m_imageIndexAcquired, image.layout);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderer::ClearWireFramebuffer(const MG_Pipe::MGPFramebufferState& fbo,
|
||||
const ClearAttachmentPayload& payload, GLint drawbuffer) {
|
||||
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("wire-verb-flush");
|
||||
if (MGB_CTX->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard)) return;
|
||||
const auto clearSurface = [&](const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect,
|
||||
Uint32 colorIndex) {
|
||||
if (!fbo.IsDefault && surface.Kind == MG_Pipe::kMGPipeSurfaceKindNone) return;
|
||||
if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) {
|
||||
const auto mask = MGB_CTX->GetColorMaskIndexed(colorIndex);
|
||||
if (!mask.r() && !mask.g() && !mask.b() && !mask.a()) return;
|
||||
if (!mask.r() || !mask.g() || !mask.b() || !mask.a()) {
|
||||
MGLOG_W_ONCE("DirectVulkan: glClear with a partial color mask is not supported");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (aspect & VK_IMAGE_ASPECT_DEPTH_BIT) {
|
||||
if (!MGB_CTX->GetDepthMask()) aspect &= ~VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
if (aspect & VK_IMAGE_ASPECT_STENCIL_BIT) {
|
||||
const Uint32 mask = MGB_CTX->GetStencilState(StencilFace::Front).WriteMask & 255u;
|
||||
if (!mask) aspect &= ~VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
else if (mask != 255u) {
|
||||
MGLOG_W_ONCE("DirectVulkan: glClear with a partial stencil write mask is not supported");
|
||||
aspect &= ~VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
}
|
||||
if (!aspect) return;
|
||||
WireImage image = ResolveWireImage(fbo, surface, aspect);
|
||||
if (!image.image) return;
|
||||
m_textureManager->FlushPendingUploads();
|
||||
VkRect2D area{{0, 0}, image.extent};
|
||||
if (MGB_CTX->IsCapabilityEnabled(CapabilityInput::ScissorTest)) {
|
||||
const auto& box = MGB_CTX->GetScissorBox();
|
||||
const Int32 x0 = std::max(box.x(), 0), y0 = std::max(box.y(), 0);
|
||||
const Int32 x1 = std::min(box.x() + box.z(), static_cast<Int32>(image.extent.width));
|
||||
const Int32 y1 = std::min(box.y() + box.w(), static_cast<Int32>(image.extent.height));
|
||||
if (x0 >= x1 || y0 >= y1) return;
|
||||
area = {{x0, y0}, {static_cast<Uint32>(x1-x0), static_cast<Uint32>(y1-y0)}};
|
||||
if (image.isDefault && !MapDefaultFramebufferReadbackRect(x0, y0, x1-x0, y1-y0,
|
||||
image.extent, m_swapchainObject.GetPreTransform(), &area.offset, &area.extent))
|
||||
MagmaWireFatal("clear-surface-transform");
|
||||
}
|
||||
const Bool color = aspect == VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
const auto layout = color ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL :
|
||||
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
TransitionWireImage(image, layout);
|
||||
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
viewInfo.image = image.image;
|
||||
viewInfo.viewType = image.layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = image.format;
|
||||
viewInfo.subresourceRange = {color ? VK_IMAGE_ASPECT_COLOR_BIT :
|
||||
(image.format == VK_FORMAT_D24_UNORM_S8_UINT || image.format == VK_FORMAT_D32_SFLOAT_S8_UINT ? VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT : image.aspect), image.level, 1, image.layer, image.layers};
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &view));
|
||||
VkAttachmentDescription attachment{};
|
||||
attachment.format = image.format;
|
||||
attachment.samples = image.samples;
|
||||
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment.initialLayout = layout;
|
||||
attachment.finalLayout = layout;
|
||||
VkAttachmentReference ref{0, layout};
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = color ? 1 : 0;
|
||||
subpass.pColorAttachments = color ? &ref : nullptr;
|
||||
subpass.pDepthStencilAttachment = color ? nullptr : &ref;
|
||||
VkRenderPassCreateInfo passInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||||
passInfo.attachmentCount = 1; passInfo.pAttachments = &attachment;
|
||||
passInfo.subpassCount = 1; passInfo.pSubpasses = &subpass;
|
||||
VkRenderPass pass = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateRenderPass(m_device, &passInfo, nullptr, &pass));
|
||||
VkFramebufferCreateInfo fbInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
fbInfo.renderPass = pass; fbInfo.attachmentCount = 1; fbInfo.pAttachments = &view;
|
||||
fbInfo.width = image.extent.width; fbInfo.height = image.extent.height; fbInfo.layers = image.layers;
|
||||
VkFramebuffer fb = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateFramebuffer(m_device, &fbInfo, nullptr, &fb));
|
||||
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||||
begin.renderPass = pass; begin.framebuffer = fb; begin.renderArea = area;
|
||||
auto clearPayload = payload;
|
||||
if (color) PreCompensateSrgbClearColor(clearPayload, image.format);
|
||||
VkClearAttachment clear{};
|
||||
clear.aspectMask = aspect;
|
||||
clear.clearValue.color = MakeVkClearColorValue(clearPayload, false);
|
||||
if (!color) clear.clearValue.depthStencil = {payload.depth, payload.stencil};
|
||||
VkClearRect rect{area, 0, image.layers};
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
vkCmdBeginRenderPass(frame.commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdClearAttachments(frame.commandBuffer, 1, &clear, 1, &rect);
|
||||
vkCmdEndRenderPass(frame.commandBuffer);
|
||||
// P5f changes ownership, not Magma's wait contract. Synchronous retirement keeps
|
||||
// these temporary server resources bounded and makes their lifetime explicit.
|
||||
if (!SubmitReadbackCommandsAndWait(frame)) MagmaWireFatal("clear-submit");
|
||||
vkDestroyFramebuffer(m_device, fb, nullptr);
|
||||
vkDestroyRenderPass(m_device, pass, nullptr);
|
||||
vkDestroyImageView(m_device, view, nullptr);
|
||||
if (!image.isDefault && surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture)
|
||||
m_textureManager->MarkWireTextureGpuWritten(image.storage, image.level, image.layer, image.layers);
|
||||
if (image.isDefault) {
|
||||
if (color) m_swapchainObject.SetImageContentDefined(m_imageIndexAcquired, true);
|
||||
else m_swapchainObject.SetDepthStencilContentDefined(m_imageIndexAcquired, true);
|
||||
}
|
||||
};
|
||||
if (payload.mask & GL_COLOR_BUFFER_BIT) {
|
||||
for (Uint32 i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i) {
|
||||
if (drawbuffer >= 0 && static_cast<Uint32>(drawbuffer) != i) continue;
|
||||
const Int32 attachment = fbo.DrawBuffers[i];
|
||||
if (attachment >= 0 && attachment < static_cast<Int32>(MG_Pipe::kMGPipeMaxColorAttachments))
|
||||
clearSurface(fbo.Color[attachment], VK_IMAGE_ASPECT_COLOR_BIT, i);
|
||||
}
|
||||
}
|
||||
if (payload.mask & GL_DEPTH_BUFFER_BIT) clearSurface(fbo.Depth, VK_IMAGE_ASPECT_DEPTH_BIT, 0);
|
||||
if (payload.mask & GL_STENCIL_BUFFER_BIT) clearSurface(fbo.Stencil, VK_IMAGE_ASPECT_STENCIL_BIT, 0);
|
||||
}
|
||||
|
||||
void VulkanRenderer::ReadWirePixels(GLint x, GLint y, GLsizei width, GLsizei height,
|
||||
GLenum format, GLenum type, void* pixels) {
|
||||
if (width <= 0 || height <= 0) return;
|
||||
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("wire-verb-flush");
|
||||
const auto* fbo = MG_Pipe::MGPipeApplier().ReadFramebuffer();
|
||||
if (!fbo) MagmaWireFatal("read-framebuffer-record");
|
||||
VkImageAspectFlags aspect = format == GL_DEPTH_COMPONENT ? VK_IMAGE_ASPECT_DEPTH_BIT :
|
||||
format == GL_STENCIL_INDEX ? VK_IMAGE_ASPECT_STENCIL_BIT :
|
||||
VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
if (format == GL_DEPTH_STENCIL) aspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
const auto& surface = (aspect & VK_IMAGE_ASPECT_DEPTH_BIT) ? fbo->Depth :
|
||||
aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? fbo->Stencil : fbo->ReadSurface;
|
||||
WireImage source = ResolveWireImage(*fbo, surface, aspect);
|
||||
if (!source.image) return;
|
||||
m_textureManager->FlushPendingUploads();
|
||||
TransitionWireImage(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
if (aspect != VK_IMAGE_ASPECT_COLOR_BIT) {
|
||||
ReadDepthStencilImageToClient(source.image,source.format,
|
||||
source.trackedLayout ? source.trackedLayout : &source.layout,aspect,
|
||||
source.level,source.layer,x,y,width,height,format,type,pixels,source.isDefault);
|
||||
if (source.isDefault) m_swapchainObject.SetDepthStencilImageLayout(m_imageIndexAcquired,source.layout);
|
||||
return;
|
||||
}
|
||||
const SizeT texelSize = aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? 1 : GetReadbackTexelSize(source.format);
|
||||
if (!texelSize) MagmaWireFatal("read-format");
|
||||
const VkDeviceSize size = static_cast<VkDeviceSize>(width) * height * texelSize;
|
||||
VkBufferObject buffer;
|
||||
if (!buffer.Create({.allocator=m_allocator, .size=size, .usage=VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
.memoryUsage=VMA_MEMORY_USAGE_AUTO, .allocationFlags=VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT}))
|
||||
MagmaWireFatal("read-buffer-allocation");
|
||||
VkOffset2D offset{x,y};
|
||||
VkExtent2D extent{static_cast<Uint32>(width), static_cast<Uint32>(height)};
|
||||
if (source.isDefault && !MapDefaultFramebufferReadbackRect(x,y,width,height,source.extent,
|
||||
m_swapchainObject.GetPreTransform(),&offset,&extent)) MagmaWireFatal("read-surface-transform");
|
||||
VkBufferImageCopy region{};
|
||||
region.imageSubresource = {aspect,source.level,source.is3D ? 0u : source.layer,1};
|
||||
region.imageOffset = {offset.x,offset.y,source.is3D ? static_cast<Int32>(source.layer) : 0};
|
||||
region.imageExtent = {extent.width,extent.height,1};
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
vkCmdCopyImageToBuffer(frame.commandBuffer,source.image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
buffer.GetHandle(),1,®ion);
|
||||
if (!SubmitReadbackCommandsAndWait(frame)) MagmaWireFatal("read-submit");
|
||||
const auto* bytes = static_cast<const Uint8*>(buffer.Map());
|
||||
if (!bytes || !buffer.Invalidate(size)) MagmaWireFatal("read-map");
|
||||
Vector<Uint8> remapped;
|
||||
if (source.isDefault) {
|
||||
remapped.resize(static_cast<SizeT>(size));
|
||||
if (!RemapDefaultFramebufferReadback(bytes,width,height,m_swapchainObject.GetPreTransform(),
|
||||
texelSize,remapped.data())) MagmaWireFatal("read-remap");
|
||||
bytes = remapped.data();
|
||||
}
|
||||
if (!PackReadbackToClientOrPbo(bytes,source.format,width,height,1,format,type,pixels,false,true))
|
||||
MagmaWireFatal("read-conversion");
|
||||
}
|
||||
|
||||
void VulkanRenderer::BlitWireFramebuffers(GLint sx0, GLint sy0, GLint sx1, GLint sy1,
|
||||
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLbitfield mask, GLenum filter) {
|
||||
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("wire-verb-flush");
|
||||
auto& state = MG_Pipe::MGPipeApplier();
|
||||
const Bool named = !MG_Pipe::MGPipeHandleIsNull(state.VerbBlitReadFbo) ||
|
||||
!MG_Pipe::MGPipeHandleIsNull(state.VerbBlitDrawFbo);
|
||||
const auto* read = named ? state.FramebufferRecordFor(state.VerbBlitReadFbo) : state.ReadFramebuffer();
|
||||
const auto* draw = named ? state.FramebufferRecordFor(state.VerbBlitDrawFbo) : state.DrawFramebuffer();
|
||||
if (!read || !draw) MagmaWireFatal("blit-framebuffer-record");
|
||||
state.VerbBlitNamedConsumed = named;
|
||||
const auto blit = [&](const MG_Pipe::MGPSurface& src, const MG_Pipe::MGPSurface& dst,
|
||||
VkImageAspectFlags aspect) {
|
||||
WireImage source = ResolveWireImage(*read,src,aspect);
|
||||
WireImage destination = ResolveWireImage(*draw,dst,aspect);
|
||||
if (!source.image || !destination.image) return;
|
||||
m_textureManager->FlushPendingUploads();
|
||||
const Bool sameImage = source.image == destination.image;
|
||||
const VkImageLayout srcLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
const VkImageLayout dstLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
TransitionWireImage(source,srcLayout);
|
||||
if (sameImage) destination.layout = source.layout;
|
||||
else TransitionWireImage(destination,dstLayout);
|
||||
VkImageBlit region{};
|
||||
region.srcSubresource = {aspect,source.level,source.layer,1};
|
||||
region.dstSubresource = {aspect,destination.level,destination.layer,1};
|
||||
region.srcOffsets[0] = {sx0,sy0,0}; region.srcOffsets[1] = {sx1,sy1,1};
|
||||
region.dstOffsets[0] = {dx0,dy0,0}; region.dstOffsets[1] = {dx1,dy1,1};
|
||||
// Headless and identity surfaces store GL's bottom row at Vulkan's last row.
|
||||
if (source.isDefault) {
|
||||
region.srcOffsets[0].y = static_cast<GLint>(source.extent.height)-sy0;
|
||||
region.srcOffsets[1].y = static_cast<GLint>(source.extent.height)-sy1;
|
||||
}
|
||||
if (destination.isDefault) {
|
||||
region.dstOffsets[0].y = static_cast<GLint>(destination.extent.height)-dy0;
|
||||
region.dstOffsets[1].y = static_cast<GLint>(destination.extent.height)-dy1;
|
||||
}
|
||||
vkCmdBlitImage(m_frameContext.GetCurrent().commandBuffer,source.image,srcLayout,
|
||||
destination.image,dstLayout,1,®ion,
|
||||
filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST);
|
||||
if (!destination.isDefault && dst.Kind == MG_Pipe::kMGPipeSurfaceKindTexture)
|
||||
m_textureManager->MarkWireTextureGpuWritten(destination.storage, destination.level, destination.layer, destination.layers);
|
||||
if (destination.isDefault) {
|
||||
if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) m_swapchainObject.SetImageContentDefined(m_imageIndexAcquired,true);
|
||||
else m_swapchainObject.SetDepthStencilContentDefined(m_imageIndexAcquired,true);
|
||||
}
|
||||
};
|
||||
if (mask & GL_COLOR_BUFFER_BIT) {
|
||||
for (Uint32 i=0;i<MG_Pipe::kMGPipeMaxColorAttachments;++i) {
|
||||
const Int32 attachment = draw->DrawBuffers[i];
|
||||
if (attachment>=0 && attachment<static_cast<Int32>(MG_Pipe::kMGPipeMaxColorAttachments))
|
||||
blit(read->ReadSurface,draw->Color[attachment],VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
}
|
||||
}
|
||||
if (mask & GL_DEPTH_BUFFER_BIT) blit(read->Depth,draw->Depth,VK_IMAGE_ASPECT_DEPTH_BIT);
|
||||
if (mask & GL_STENCIL_BUFFER_BIT) blit(read->Stencil,draw->Stencil,VK_IMAGE_ASPECT_STENCIL_BIT);
|
||||
}
|
||||
void VulkanRenderer::CopyWireFramebufferToTexture(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||
GLint x, GLint y, GLsizei width, GLsizei height) {
|
||||
if (width <= 0 || height <= 0) return;
|
||||
if (target != GL_TEXTURE_2D) {
|
||||
RecordTextureCopyError(__func__,ErrorCode::InvalidOperation,
|
||||
"CopyTexSubImage2D currently only supports GL_TEXTURE_2D destinations.");
|
||||
return;
|
||||
}
|
||||
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("wire-verb-flush");
|
||||
auto& state = MG_Pipe::MGPipeApplier();
|
||||
const auto* read = state.ReadFramebuffer();
|
||||
if (!read) MagmaWireFatal("copy-framebuffer-record");
|
||||
const auto* texture = m_textureManager->SyncTextureResourceByHandle(state.VerbCopyTexDst);
|
||||
if (!texture) MagmaWireFatal("copy-destination-record");
|
||||
const VkImageAspectFlags aspect = texture->aspect;
|
||||
MG_Pipe::MGPFramebufferState fake{};
|
||||
MG_Pipe::MGPSurface surface{};
|
||||
surface.Kind = MG_Pipe::kMGPipeSurfaceKindTexture;
|
||||
surface.Res = state.VerbCopyTexDst;
|
||||
surface.Level = static_cast<Uint16>(level);
|
||||
WireImage source = ResolveWireImage(*read,aspect & VK_IMAGE_ASPECT_DEPTH_BIT ? read->Depth :
|
||||
aspect & VK_IMAGE_ASPECT_STENCIL_BIT ? read->Stencil : read->ReadSurface,aspect);
|
||||
WireImage destination = ResolveWireImage(fake,surface,aspect);
|
||||
if (!source.image || !destination.image) return;
|
||||
m_textureManager->FlushPendingUploads();
|
||||
TransitionWireImage(source,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
TransitionWireImage(destination,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
VkImageBlit region{};
|
||||
region.srcSubresource = {aspect,source.level,source.layer,1};
|
||||
region.dstSubresource = {aspect,destination.level,destination.layer,1};
|
||||
region.srcOffsets[0] = {x,y,0}; region.srcOffsets[1] = {x+width,y+height,1};
|
||||
region.dstOffsets[0] = {xoffset,yoffset,0}; region.dstOffsets[1] = {xoffset+width,yoffset+height,1};
|
||||
if (source.isDefault) {
|
||||
region.srcOffsets[0].y = static_cast<Int32>(source.extent.height)-y;
|
||||
region.srcOffsets[1].y = static_cast<Int32>(source.extent.height)-y-height;
|
||||
}
|
||||
vkCmdBlitImage(m_frameContext.GetCurrent().commandBuffer,source.image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
destination.image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,®ion,VK_FILTER_NEAREST);
|
||||
m_textureManager->MarkWireTextureGpuWritten(destination.storage,destination.level,destination.layer);
|
||||
}
|
||||
|
||||
void VulkanRenderer::GenerateWireMipmap() {
|
||||
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("wire-verb-flush");
|
||||
auto& state = MG_Pipe::MGPipeApplier();
|
||||
const auto handle = state.VerbMipRes;
|
||||
auto* resource = m_textureManager->SyncTextureResourceByHandle(handle);
|
||||
if (!resource) MagmaWireFatal("mipmap-resource");
|
||||
m_textureManager->FlushPendingUploads();
|
||||
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("mipmap-flush");
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording();
|
||||
const Uint32 base = state.VerbMipBaseLevel;
|
||||
const Uint32 count = state.VerbMipLevelCount;
|
||||
if (count <= 1) return;
|
||||
if (!m_textureManager->GrowWireTextureMipChain(handle,base+count,frame.commandBuffer))
|
||||
MagmaWireFatal("mipmap-growth");
|
||||
if (resource->aspect != VK_IMAGE_ASPECT_COLOR_BIT && resource->aspect != VK_IMAGE_ASPECT_DEPTH_BIT)
|
||||
MagmaWireFatal("depth-stencil-mipmap@P7");
|
||||
VkFormatProperties props{};
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle,resource->format,&props);
|
||||
const auto required = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
|
||||
if ((props.optimalTilingFeatures & required) != required) MagmaWireFatal("mipmap-native-format@P7");
|
||||
const VkFilter filter = props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT ?
|
||||
VK_FILTER_LINEAR : VK_FILTER_NEAREST;
|
||||
VkPipelineStageFlags stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags access = 0;
|
||||
GetImageTransitionSourceState(resource->layout,stage,access);
|
||||
if (!VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,resource->layout,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,stage,VK_PIPELINE_STAGE_TRANSFER_BIT,access,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,0,resource->mipLevels)) MagmaWireFatal("mipmap-transition");
|
||||
for (Uint32 level=base+1;level<base+count;++level) {
|
||||
VkImageLayout previous = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,previous,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT,resource->aspect,level-1,1);
|
||||
VkImageBlit blit{};
|
||||
blit.srcSubresource = {resource->aspect,level-1,0,resource->arrayLayers};
|
||||
blit.dstSubresource = {resource->aspect,level,0,resource->arrayLayers};
|
||||
blit.srcOffsets[1] = {static_cast<Int32>(std::max(resource->extent.width>>(level-1),1u)),
|
||||
static_cast<Int32>(std::max(resource->extent.height>>(level-1),1u)),
|
||||
static_cast<Int32>(std::max(resource->depth>>(level-1),1u))};
|
||||
blit.dstOffsets[1] = {static_cast<Int32>(std::max(resource->extent.width>>level,1u)),
|
||||
static_cast<Int32>(std::max(resource->extent.height>>level,1u)),
|
||||
static_cast<Int32>(std::max(resource->depth>>level,1u))};
|
||||
vkCmdBlitImage(frame.commandBuffer,resource->image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
resource->image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,&blit,filter);
|
||||
VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,previous,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,level-1,1);
|
||||
}
|
||||
resource->layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
auto& record = state.TextureResources[handle.Slot];
|
||||
record.Desc.Levels = std::max<Uint16>(record.Desc.Levels,static_cast<Uint16>(base+count));
|
||||
resource->syncedWireSerial = ++record.Serial;
|
||||
auto& store = MG_Remote::Server::ServerStagedTexture();
|
||||
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(handle);
|
||||
const auto target = static_cast<MG_Pipe::MGPipeResourceTarget>(record.Desc.Target);
|
||||
// The resource descriptor determines the upload-target set; no texture unit or
|
||||
// client upload-target vector participates in defining the generated shadow.
|
||||
for (Uint32 face=0;face<(target==MG_Pipe::MGPipeResourceTarget::TexCube ? 6u : 1u);++face) {
|
||||
TextureUploadTarget upload = TextureUploadTarget::Texture2D;
|
||||
switch (target) {
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex1D: upload=TextureUploadTarget::Texture1D; break;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex3D: upload=TextureUploadTarget::Texture3D; break;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex1DArray: upload=TextureUploadTarget::Texture1DArray; break;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex2DArray: upload=TextureUploadTarget::Texture2DArray; break;
|
||||
case MG_Pipe::MGPipeResourceTarget::TexCubeArray: upload=TextureUploadTarget::CubeMapArray; break;
|
||||
case MG_Pipe::MGPipeResourceTarget::TexCube:
|
||||
upload=static_cast<TextureUploadTarget>(static_cast<Uint32>(TextureUploadTarget::CubeMapPositiveX)+face); break;
|
||||
default: break;
|
||||
}
|
||||
for (Uint32 level=base+1;level<base+count;++level) {
|
||||
const IntVec3 extent{static_cast<Int>(std::max(resource->extent.width>>level,1u)),
|
||||
static_cast<Int>(std::max(resource->extent.height>>level,1u)),
|
||||
static_cast<Int>(std::max(resource->depth>>level,1u))};
|
||||
store.NoteLevelDefined(key,static_cast<Uint16>(upload),static_cast<Uint16>(level),extent);
|
||||
store.MarkLevelGpuDirty(key,static_cast<Uint16>(upload),static_cast<Uint16>(level),true);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -113,7 +113,8 @@ namespace MobileGL::MG_Backend {
|
||||
switch (type) {
|
||||
case BackendType::DirectGLES: return MG_Pipe::kMGPipeSubsystemsMigratedAtP5e;
|
||||
case BackendType::DirectVulkan:
|
||||
return MG_Pipe::kMGPipeSubsystemsMigratedAtP4a & ~MG_Pipe::kMGPipeSubsystemResources;
|
||||
return (MG_Pipe::kMGPipeSubsystemsMigratedAtP4a | MG_Pipe::kMGPipeSubsystemBufferBindings) &
|
||||
~MG_Pipe::kMGPipeSubsystemResources;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6077,6 +6077,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
viewObject->SetInternalFormat(viewInternalFormat);
|
||||
viewObject->SetSamples(storageOwner->GetSamples());
|
||||
viewObject->SetFixedSampleLocations(storageOwner->HasFixedSampleLocations());
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith)
|
||||
MG_Pipe::MGPipeEmitSamplerViewCreate(*viewObject);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TexStorage1D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width) {
|
||||
|
||||
@@ -1392,8 +1392,10 @@ namespace MobileGL::MG_Pipe {
|
||||
// family here would be a NEW rule, and a client that withheld more than the server
|
||||
// refuses leaves the server's handle arm live with no records to read.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
// P5f fm: texture/framebuffer records have their own server consumers;
|
||||
// Magma consumes them without claiming the still-unmigrated buffer family.
|
||||
return MG_Remote::Client::CapsMirrorInstance().ServerConsumes(
|
||||
kMGPipeSubsystemResources);
|
||||
subsystem & kMGPipeP4aFamilySubsystems);
|
||||
}
|
||||
#endif
|
||||
return MGPipeGetResourceOps() != nullptr;
|
||||
@@ -2832,6 +2834,9 @@ namespace MobileGL::MG_Pipe {
|
||||
Bool ApplierDerives = false;
|
||||
Bool ContextValuesWireLive = false;
|
||||
Bool P4aConsumer = false;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Uint64 CapsGeneration = 0;
|
||||
#endif
|
||||
// One bit per FIELD - not per verb class. The class mask is applied at the walk
|
||||
// exactly as it always was, so "does this verb read this field" stays the walk's
|
||||
// business and this stays a statement about EMISSION alone.
|
||||
@@ -2857,7 +2862,12 @@ namespace MobileGL::MG_Pipe {
|
||||
if (g_fillPlan.Valid && g_fillPlan.PushMask == pushMask &&
|
||||
g_fillPlan.ApplierDerives == applierDerives &&
|
||||
g_fillPlan.ContextValuesWireLive == contextValuesWireLive &&
|
||||
g_fillPlan.P4aConsumer == p4aConsumer) {
|
||||
g_fillPlan.P4aConsumer == p4aConsumer
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
&& g_fillPlan.CapsGeneration == (MG_Config::Transport != MG_Config::TransportMode::Monolith ?
|
||||
MG_Remote::Client::CapsMirrorInstance().Generation() : 0)
|
||||
#endif
|
||||
) {
|
||||
return g_fillPlan.Supplied;
|
||||
}
|
||||
MGPipeFieldMask built{};
|
||||
@@ -2898,6 +2908,10 @@ namespace MobileGL::MG_Pipe {
|
||||
g_fillPlan.ApplierDerives = applierDerives;
|
||||
g_fillPlan.ContextValuesWireLive = contextValuesWireLive;
|
||||
g_fillPlan.P4aConsumer = p4aConsumer;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
g_fillPlan.CapsGeneration = MG_Config::Transport != MG_Config::TransportMode::Monolith ?
|
||||
MG_Remote::Client::CapsMirrorInstance().Generation() : 0;
|
||||
#endif
|
||||
g_fillPlan.Supplied = built;
|
||||
return built;
|
||||
}
|
||||
|
||||
@@ -2378,6 +2378,21 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
ENVIRONMENT "${MGL_ITEST_GLES_SPLIT_ENVIRONMENT}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p5b-f1-${f1Slot}.log"
|
||||
)
|
||||
endforeach()
|
||||
# P5f fm: the framebuffer/copy/mipmap verbs now consume server records on Magma too.
|
||||
mgl_itest_join_environment(MGL_ITEST_MAGMA_WIRE_ENVIRONMENT
|
||||
"MOBILEGL_BACKEND_TYPE=DirectVulkan" "MOBILEGL_TRANSPORT=inproc"
|
||||
"MOBILEGL_ITEST_REQUIRE_GPU=1" "MGITEST_SPLIT_LANE=1"
|
||||
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV})
|
||||
foreach(fmSlot PartialTextureUploadPreservesGpuClear TextureViewClearTargetsItsRootMipAndLayer ClearBufferfi ClearBufferfv ClearBufferiv ClearBufferuiv ClearNamedFramebufferfi ClearNamedFramebufferfv ClearNamedFramebufferiv ClearNamedFramebufferuiv CopyTexImage2D CopyTexSubImage2D GenerateMipmap GenerateMipmapPackedFloat GenerateMipmapDepth)
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.Split.Fm."
|
||||
TEST_FILTER "F1WireScenario.${fmSlot}Pixels"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES
|
||||
LABELS "integration-magma-split"
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
ENVIRONMENT "${MGL_ITEST_MAGMA_WIRE_ENVIRONMENT}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p5f-fm-${fmSlot}.log")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# P5c ct (MG_Remote/CONTRACT-P5C.md §5): the two control records, split-only source for the
|
||||
|
||||
@@ -259,6 +259,84 @@ TEST_F(F1WireScenario, GenerateMipmapDepthPixels) {
|
||||
EXPECT_NEAR(pixel, 0.375f, 0.00001f);
|
||||
}
|
||||
|
||||
TEST_F(F1WireScenario, PartialTextureUploadPreservesGpuClearPixels) {
|
||||
if (!Ready()) return;
|
||||
Attach(GL_RGBA8);
|
||||
// The client shadow is red. The GPU then changes every texel to green, so
|
||||
// applying a later one-pixel upload as a whole shadow loses observable data.
|
||||
std::array<GLubyte, 8 * 8 * 4> red{};
|
||||
for (size_t i = 0; i < red.size(); i += 4) {
|
||||
red[i] = 255;
|
||||
red[i + 3] = 255;
|
||||
}
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, red.data());
|
||||
glClearColor(0, 1, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
const std::array<GLubyte, 4> blue{0, 0, 255, 255};
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, blue.data());
|
||||
|
||||
std::array<GLubyte, 8 * 8 * 4> pixels{};
|
||||
glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
for (int y = 0; y < 8; ++y) {
|
||||
for (int x = 0; x < 8; ++x) {
|
||||
const std::array<GLubyte, 4> expected = x == 1 && y == 2
|
||||
? blue : std::array<GLubyte, 4>{0, 255, 0, 255};
|
||||
const size_t at = static_cast<size_t>(y * 8 + x) * 4;
|
||||
const std::array<GLubyte, 4> actual{pixels[at], pixels[at + 1], pixels[at + 2], pixels[at + 3]};
|
||||
EXPECT_EQ(actual, expected) << "GPU clear survived outside upload at " << x << ", " << y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(F1WireScenario, TextureViewClearTargetsItsRootMipAndLayer) {
|
||||
if (!Ready()) return;
|
||||
GLuint root = 0, view = 0;
|
||||
glGenTextures(1, &root);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, root);
|
||||
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, 8, 8, 2);
|
||||
std::array<GLubyte, 8 * 8 * 2 * 4> red{};
|
||||
for (size_t i = 0; i < red.size(); i += 4) {
|
||||
red[i] = 255;
|
||||
red[i + 3] = 255;
|
||||
}
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 2,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, red.data());
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 2,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, red.data());
|
||||
std::array<GLubyte, 4 * 4 * 4> blue{};
|
||||
for (size_t i = 0; i < blue.size(); i += 4) {
|
||||
blue[i + 2] = 255;
|
||||
blue[i + 3] = 255;
|
||||
}
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 1,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, blue.data());
|
||||
glGenTextures(1, &view);
|
||||
glTextureView(view, GL_TEXTURE_2D, root, GL_RGBA8, 1, 1, 1, 1);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "view construction";
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, view, 0);
|
||||
ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE));
|
||||
glClearColor(0, 1, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
std::array<GLubyte, 4> pixel{};
|
||||
glReadPixels(1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data());
|
||||
EXPECT_EQ(pixel, (std::array<GLubyte, 4>{0, 255, 0, 255})) << "view reads its cleared window";
|
||||
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 1, 1);
|
||||
glReadPixels(1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data());
|
||||
EXPECT_EQ(pixel, (std::array<GLubyte, 4>{0, 255, 0, 255})) << "view clear reaches root mip 1, layer 1";
|
||||
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 1, 0);
|
||||
glReadPixels(1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data());
|
||||
EXPECT_EQ(pixel, (std::array<GLubyte, 4>{0, 0, 255, 255})) << "the neighboring root layer is preserved";
|
||||
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 0, 1);
|
||||
glReadPixels(1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data());
|
||||
EXPECT_EQ(pixel, (std::array<GLubyte, 4>{255, 0, 0, 255})) << "the neighboring root mip is preserved";
|
||||
EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0);
|
||||
glDeleteTextures(1, &view);
|
||||
glDeleteTextures(1, &root);
|
||||
}
|
||||
|
||||
TEST_F(F1WireScenario, NamedBlitPreservesBindingsAndRestoresNextVerbsPixels) {
|
||||
if (!Ready()) return;
|
||||
Attach(GL_RGBA8);
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
// server-role-only fixture: there the direct call is the only reset that exists).
|
||||
#include <MG_Remote/Client/ClientSession.h>
|
||||
#include <MG_Remote/Server/ServerLoop.h>
|
||||
#include <MG_Remote/Server/ServerSession.h>
|
||||
#include <MG_Remote/CapsCodec.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
@@ -1235,6 +1237,13 @@ namespace MobileGL::MG_Pipe {
|
||||
// site: ApplyUnitWindow validates and writes in one step, so the question has to be
|
||||
// asked in front of it.
|
||||
Bool NoP4aConsumer() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const auto* session = MG_Remote::Server::ServerSession::Active();
|
||||
if (session && session->CallMaskIsSet() && MG_Remote::MGCapsServerConsumes(
|
||||
session->CallMask(), kMGPipeSubsystemTextureResources)) return false;
|
||||
}
|
||||
#endif
|
||||
if (g_resourceOps != nullptr) return false;
|
||||
++g_applier.RefusedNoConsumer;
|
||||
return true;
|
||||
|
||||
@@ -851,6 +851,8 @@ namespace MobileGL::MG_Pipe {
|
||||
UnorderedMap<Uint64, MGPStreamOutputBegin> StreamOutputSpans;
|
||||
// generate_mipmap's texture (MGPMipPlan::Res).
|
||||
MGPipeHandle VerbMipRes = kMGPipeNullHandle;
|
||||
Uint16 VerbMipBaseLevel = 0;
|
||||
Uint16 VerbMipLevelCount = 0;
|
||||
// The current indirect draw's command buffer and (for the *Count forms) parameter
|
||||
// buffer (MGPDrawIndirect::Buffer / ParameterBuffer).
|
||||
MGPipeHandle VerbIndirectBuffer = kMGPipeNullHandle;
|
||||
@@ -875,6 +877,7 @@ namespace MobileGL::MG_Pipe {
|
||||
VerbCopyTexDst = kMGPipeNullHandle;
|
||||
VerbStorageBlockProgram = kMGPipeNullHandle;
|
||||
VerbMipRes = kMGPipeNullHandle;
|
||||
VerbMipBaseLevel = VerbMipLevelCount = 0;
|
||||
VerbIndirectBuffer = kMGPipeNullHandle;
|
||||
VerbIndirectParameterBuffer = kMGPipeNullHandle;
|
||||
VerbDispatchIndirectBuffer = kMGPipeNullHandle;
|
||||
|
||||
@@ -1047,6 +1047,8 @@ namespace MobileGL::MG_Remote::Server {
|
||||
auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
applierState.ClearVerbHandles();
|
||||
applierState.VerbMipRes = plan.Res;
|
||||
applierState.VerbMipBaseLevel = plan.BaseLevel;
|
||||
applierState.VerbMipLevelCount = plan.LevelCount;
|
||||
#endif
|
||||
table->GL.GenerateMipmap(plan.Target);
|
||||
return true;
|
||||
|
||||
@@ -44,6 +44,9 @@
|
||||
#include <Config.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/MagmaPipeArms.h>
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Backend/DirectVulkan/Renderer/MagmaProgramSource.h>
|
||||
#endif
|
||||
|
||||
using namespace MobileGL;
|
||||
|
||||
@@ -258,4 +261,99 @@ namespace {
|
||||
"kCapRunAheadApply moved bit; CONTRACT-P5E table 0 names bit 10");
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
|
||||
TEST(MagmaProgramSourceTest, ServerBindingTailsReplaceLinkTimeDefaults) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
auto archive = MakeShared<MG_State::GLState::ProgramArchive>();
|
||||
archive->Link.glBlockIndexToTProgram = {0};
|
||||
archive->Link.blockReflection.resize(1);
|
||||
archive->Link.blockReflection[0].name = "Block";
|
||||
archive->Link.blockReflection[0].size = 12;
|
||||
archive->Link.uniformBlockBinding = {9};
|
||||
archive->Link.uniformSamplerOrImageUnitIndex = {12, 13};
|
||||
archive->Spirv.globalUboScratch = {99, 98};
|
||||
MG_Pipe::MGPipeShaderCsoRecord record{};
|
||||
record.Archive = archive;
|
||||
record.BlockBindings = {3};
|
||||
record.SamplerUnits = {{0, 4}};
|
||||
record.GlobalConstants = {5, 6};
|
||||
record.GlobalConstantsVersion = 11;
|
||||
const MagmaProgramSource source({7, 2}, record);
|
||||
EXPECT_TRUE(source.IsWire());
|
||||
EXPECT_EQ(source.Frontend(), nullptr);
|
||||
EXPECT_EQ(source.GetUniformBlockBinding(0), 3u);
|
||||
EXPECT_EQ(source.GetUniformBlockName(0), "Block");
|
||||
EXPECT_EQ(source.GetUBOSizeAt(0), 16u);
|
||||
EXPECT_EQ(source.GetUniformSamplerOrImageUnitIndex(0), 4);
|
||||
EXPECT_EQ(source.GetUniformSamplerOrImageUnitIndex(1), -1)
|
||||
<< "an absent tail entry must not resurrect the archive's stale unit";
|
||||
ASSERT_EQ(source.GetUBOSize(), 2u);
|
||||
EXPECT_EQ(static_cast<const Uint8*>(source.GetUBOData())[0], 5u);
|
||||
EXPECT_EQ(source.GetUBOContentVersion(), 11u);
|
||||
record.BlockBindings[0] = 6;
|
||||
record.SamplerUnits[0].Unit = 8;
|
||||
record.GlobalConstants[0] = 42;
|
||||
record.GlobalConstantsVersion = 12;
|
||||
EXPECT_EQ(source.GetUniformBlockBinding(0), 6u);
|
||||
EXPECT_EQ(source.GetUniformSamplerOrImageUnitIndex(0), 8);
|
||||
EXPECT_EQ(static_cast<const Uint8*>(source.GetUBOData())[0], 42u);
|
||||
EXPECT_EQ(source.GetUBOContentVersion(), 12u);
|
||||
#else
|
||||
GTEST_SKIP() << "server program sources require the disaggregated build";
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(MagmaProgramSourceTest, ArrayUniformNamesResolveWithinTheArchivedUniform) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
auto archive = MakeShared<MG_State::GLState::ProgramArchive>();
|
||||
auto& link = archive->Link;
|
||||
link.maxUniformLocation = 2;
|
||||
link.uniformLocations["arr[0]"] = 0;
|
||||
link.uniformIndexInTProgram = {0, 0, 0};
|
||||
link.tProgramUniformIndexToGl = {0};
|
||||
link.uniformReflection.resize(1);
|
||||
link.uniformReflection[0].type.isArray = true;
|
||||
link.uniformReflection[0].arraySize = 3;
|
||||
link.uniformReflection[0].glDefineType = GL_SAMPLER_2D;
|
||||
MG_Pipe::MGPipeShaderCsoRecord record{};
|
||||
record.Archive = archive;
|
||||
const MagmaProgramSource source({8, 1}, record);
|
||||
EXPECT_EQ(source.GetUniformLocation("arr"), 0);
|
||||
EXPECT_EQ(source.GetUniformLocation("arr[2]"), 2);
|
||||
EXPECT_EQ(source.GetUniformLocation("arr[3]"), -1);
|
||||
EXPECT_EQ(source.GetUniformLocation("arr[-1]"), -1);
|
||||
EXPECT_EQ(source.GetUniformLocation("arr[999999999999999]"), -1);
|
||||
EXPECT_EQ(source.GetUniformLocation("unknown"), -1);
|
||||
EXPECT_TRUE(source.UniformLocationsAliasSameUniform(0, 2));
|
||||
EXPECT_FALSE(source.UniformLocationsAliasSameUniform(0, 3));
|
||||
EXPECT_EQ(source.GetUniformType(2), GL_SAMPLER_2D);
|
||||
#else
|
||||
GTEST_SKIP() << "server program sources require the disaggregated build";
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(MagmaProgramSourceTest, ReusedHandlesAndRecordVersionsHaveDistinctCacheIdentity) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Pipe::MGPipeShaderCsoRecord record{};
|
||||
record.Archive = MakeShared<MG_State::GLState::ProgramArchive>();
|
||||
record.Serial = 17;
|
||||
record.BindingsSerial = 21;
|
||||
record.GlobalConstantsVersion = 25;
|
||||
const MagmaProgramSource first({9, 2}, record);
|
||||
const MagmaProgramSource recycled({9, 3}, record);
|
||||
EXPECT_NE(first.GetLifetimeId(), recycled.GetLifetimeId());
|
||||
EXPECT_EQ(first.Handle(), (MG_Pipe::MGPipeHandle{9, 2}));
|
||||
const auto identity = first.GetLifetimeId();
|
||||
record.Serial = 18;
|
||||
record.BindingsSerial = 22;
|
||||
record.GlobalConstantsVersion = 26;
|
||||
EXPECT_EQ(first.GetLifetimeId(), identity) << "content changes do not mint object identities";
|
||||
EXPECT_EQ(first.GetBackendStateVersion(), 18u);
|
||||
EXPECT_EQ(first.GetBlockBindingVersion(), 22u);
|
||||
EXPECT_EQ(first.GetImageUnitVersion(), 22u);
|
||||
EXPECT_EQ(first.GetUBOContentVersion(), 26u);
|
||||
#else
|
||||
GTEST_SKIP() << "server program sources require the disaggregated build";
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user