mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Test] (P8-SV): WireMipShapeScenario (3D chain on both backends x every arm, 1D depth decline on Magma split+spawn, the 3D chain under the validation layer inproc) and WireIndirectDispatchScenario (both backends x every arm, Magma gated .IndirectDispatch.), the MipmapDepthStencilAspect tally in F1's depth/stencil decline case, WireDeclinePeek, and the two parity rows
This commit is contained in:
@@ -4863,6 +4863,80 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# P8-SV: Magma split residuals (docs/Disaggregated/notes/p8/SV.md) - the 3D native mip blit's layer
|
||||
# count, the 1D depth mip decline, the depth/stencil mip decline's tally row (asserted inside
|
||||
# F1WireScenario) and the wire arm's native glDispatchComputeIndirect. Public GL, so the sources join
|
||||
# the binary unguarded and the ambient `DirectGLES.` / `DirectVulkan.` monolith discovery runs them
|
||||
# too (the Espryt entries are the control). Split arms: DirectGLES through the registration macro
|
||||
# (which also replays them on Magma's informational tier), Magma's gated tier by hand:
|
||||
# `.Mip3D.` and `.IndirectDispatch.` - all three transports; the counter halves read only on the
|
||||
# inproc arm (WireIndirectPeek.h, WireDeclinePeek.h), the pixel/grid halves everywhere.
|
||||
# `.ShaderMipDepth1D.` - split + spawn: MGITEST_MAGMA_FORCE_SHADER_MIPMAP is read BY THE SERVER,
|
||||
# so a tcp copy would run the default arm (its tail is in spawn_lane_parity.py's
|
||||
# MAGMA_SERVER_ENV_KNOB_NO_TCP).
|
||||
# `.Mip3DValidation.` - inproc only: the 3D chain under VK_LAYER_KHRONOS_validation, red on any
|
||||
# vkCmdBlitImage VUID in the output. The server's Vulkan instance must be this process for its
|
||||
# stdout to be the entry's and for the case to prove the layer loaded (/proc/self/maps), so it
|
||||
# is in MAGMA_INPROC_ONLY. The lane that runs integration-magma-split in CI installs the layer.
|
||||
target_sources(MobileGLIntegrationTest PRIVATE Harness/WireDeclinePeek.cpp Scenarios/WireMipShapeScenario.cpp
|
||||
Scenarios/WireIndirectDispatchScenario.cpp)
|
||||
if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
mgl_itest_register_split_arms("WireMipShapeScenario.*" "")
|
||||
mgl_itest_register_split_arms("WireIndirectDispatchScenario.*" "")
|
||||
set(MGL_ITEST_MAGMA_SV_FLAVOUR_ENV
|
||||
"MOBILEGL_BACKEND_TYPE=DirectVulkan"
|
||||
"MOBILEGL_IPC_ROLE_SPLIT_STATE=1" "MOBILEGL_IPC_STRICT_ERRORS=1"
|
||||
"MOBILEGL_ITEST_REQUIRE_GPU=1" "MGITEST_SPLIT_LANE=1"
|
||||
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_VULKAN_ENV})
|
||||
foreach(mglItestArm IN LISTS MGL_ITEST_MAGMA_ARMS)
|
||||
mgl_itest_magma_arm_shape(${mglItestArm})
|
||||
mgl_itest_join_environment(MGL_ITEST_MAGMA_SV_ENVIRONMENT
|
||||
${MGL_ITEST_MAGMA_SV_FLAVOUR_ENV} ${mglItestArmTransportEnv})
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.Mip3D."
|
||||
TEST_FILTER "WireMipShapeScenario.VolumeChainIsGeneratedAlongItsDepth"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}"
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${MGL_ITEST_MAGMA_SV_ENVIRONMENT}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/magma-sv-mip3d${mglItestArmLogTag}.log")
|
||||
foreach(dispatchCase
|
||||
CpuWrittenGroupCountsAreReadByTheGpuAtTheirOffsets
|
||||
ComputeWrittenGroupCountsDispatchWithoutAHostWait
|
||||
ShaderWrittenGroupCountsWithoutAnApplicationBarrierAreOrdered)
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.IndirectDispatch."
|
||||
TEST_FILTER "WireIndirectDispatchScenario.${dispatchCase}"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}"
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${MGL_ITEST_MAGMA_SV_ENVIRONMENT}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/magma-sv-dispatch${mglItestArmLogTag}-${dispatchCase}.log")
|
||||
endforeach()
|
||||
if (NOT mglItestArm STREQUAL "Tcp")
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.ShaderMipDepth1D."
|
||||
TEST_FILTER "WireMipShapeScenario.Depth1DChainWithoutANativeBlitDeclinesByName"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}"
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${MGL_ITEST_MAGMA_SV_ENVIRONMENT}\;MGITEST_MAGMA_FORCE_SHADER_MIPMAP=1\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/magma-sv-depth1d${mglItestArmLogTag}.log")
|
||||
endif()
|
||||
endforeach()
|
||||
mgl_itest_magma_arm_shape(Split)
|
||||
mgl_itest_join_environment(MGL_ITEST_MAGMA_SV_ENVIRONMENT
|
||||
${MGL_ITEST_MAGMA_SV_FLAVOUR_ENV} ${mglItestArmTransportEnv})
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.Split.Mip3DValidation."
|
||||
TEST_FILTER "WireMipShapeScenario.VolumeChainIsGeneratedAlongItsDepth"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}"
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
FAIL_REGULAR_EXPRESSION "VUID-vkCmdBlitImage-"
|
||||
ENVIRONMENT "${MGL_ITEST_MAGMA_SV_ENVIRONMENT}\;VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation\;MGITEST_REQUIRE_VK_VALIDATION_LAYER=1\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/magma-sv-mip3d-validation.log")
|
||||
endif()
|
||||
|
||||
# =====================================================================================
|
||||
# P7 PACKAGE L: THE INFORMATIONAL MAGMA CENSUS - TIER 2 OF TWO
|
||||
# =====================================================================================
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Harness/WireDeclinePeek.cpp
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "WireDeclinePeek.h"
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#include <Config.h>
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Backend/DirectVulkan/Renderer/WireDeclineTally.h>
|
||||
#include <cstring>
|
||||
#define MGITEST_WIRE_DECLINE_PEEK_LIVE 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace MGITest {
|
||||
|
||||
#if defined(MGITEST_WIRE_DECLINE_PEEK_LIVE)
|
||||
bool PeekWireDeclineCount(const char* row, unsigned long long* out) {
|
||||
if (row == nullptr || out == nullptr) return false;
|
||||
namespace Magma = MobileGL::MG_Backend::DirectVulkan;
|
||||
for (MobileGL::SizeT i = 0; i < Magma::WireDeclineTally::kSiteCount; ++i) {
|
||||
const auto site = static_cast<Magma::WireDeclineSite>(i);
|
||||
if (std::strcmp(Magma::WireDeclineSiteName(site), row) != 0) continue;
|
||||
*out = Magma::WireDeclineTally::Get(site);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
bool PeekWireDeclineCount(const char*, unsigned long long*) { return false; }
|
||||
#endif
|
||||
|
||||
} // namespace MGITest
|
||||
@@ -0,0 +1,20 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Harness/WireDeclinePeek.h
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
//
|
||||
// P8-SV: one row of the Magma wire arm's decline tally (WireDeclines.def, the counts behind the
|
||||
// server's MGWIRE-DECLINES lines), read as a run total out of THIS process - WireIndirectPeek.h's
|
||||
// shape. Process-wide means the SERVER's counts only where the server is this process: the inproc
|
||||
// arm, never spawn or tcp, and never the monolith arm, which takes no wire path. False where the
|
||||
// reading cannot be taken: a pull build, Android, or a name that is not a row.
|
||||
#pragma once
|
||||
|
||||
namespace MGITest {
|
||||
|
||||
bool PeekWireDeclineCount(const char* row, unsigned long long* out);
|
||||
|
||||
} // namespace MGITest
|
||||
@@ -28,6 +28,7 @@ namespace MGITest {
|
||||
out->barriers = Stats::TotalCalls(Stats::CallClass::WireIndirectBarriers);
|
||||
out->hostWaits = Stats::TotalCalls(Stats::CallClass::WireHostWaits);
|
||||
out->indirectWaits = Stats::TotalCalls(Stats::CallClass::WireHostWaitsIndirect);
|
||||
out->nativeDispatches = Stats::TotalCalls(Stats::CallClass::WireIndirectNativeDispatches);
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace MGITest {
|
||||
unsigned long long barriers = 0; // wire-indirect-barriers (`wibar`)
|
||||
unsigned long long hostWaits = 0; // wire-host-waits (`whw`)
|
||||
unsigned long long indirectWaits = 0; // wire-host-waits-indirect (`whwi`)
|
||||
unsigned long long nativeDispatches = 0; // P8-SV: wire-indirect-native-dispatches (`wdsp`)
|
||||
};
|
||||
|
||||
bool PeekWireIndirectCounters(WireIndirectCounters* out);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../Harness/SplitRuntimePeek.h"
|
||||
#include "../Harness/PipeStatsWindow.h"
|
||||
#include "../Harness/SplitLane.h"
|
||||
#include "../Harness/WireDeclinePeek.h"
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
@@ -659,6 +660,12 @@ TEST_F(F1WireScenario, GenerateMipmapDepthStencilDeclinesAndKeepsTheSession) {
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glClearDepth(0.5);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
// P8-SV: the decline is a WireDeclines.def row now (MipmapDepthStencilAspect), so the server's
|
||||
// MGWIRE-DECLINES dump sees it. Readable where the server is this process: the inproc arm.
|
||||
// Red once (executed, reverted): the site back to a bare MGLOG_E_ONCE - the count stays 0.
|
||||
unsigned long long declinesBefore = 0;
|
||||
const bool counted = PeekSplitRuntime().transportName == "inproc" &&
|
||||
PeekWireDeclineCount("MipmapDepthStencilAspect", &declinesBefore);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DepthStencilMip.error";
|
||||
// The session is what the retired Fatal used to take with it. A readback is a round trip
|
||||
@@ -669,6 +676,12 @@ TEST_F(F1WireScenario, GenerateMipmapDepthStencilDeclinesAndKeepsTheSession) {
|
||||
glReadPixels(1, 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &level0);
|
||||
EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DepthStencilMip.readback";
|
||||
EXPECT_NEAR(level0, 0.5f, 0.002f) << "F1.DepthStencilMip.level0";
|
||||
if (counted) {
|
||||
unsigned long long declinesAfter = 0;
|
||||
ASSERT_TRUE(PeekWireDeclineCount("MipmapDepthStencilAspect", &declinesAfter));
|
||||
EXPECT_EQ(declinesAfter - declinesBefore, 1u) << "F1.DepthStencilMip.tally: one named decline per refused "
|
||||
"glGenerateMipmap (WireDeclines.def MipmapDepthStencilAspect)";
|
||||
}
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
||||
glDeleteTextures(1, &depthStencil);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDispatchScenario.cpp
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
//
|
||||
// P8-SV: THE MAGMA WIRE ARM ISSUES glDispatchComputeIndirect FROM THE WIRE STORE, AS THE MONOLITH
|
||||
// ARM DOES (docs/Disaggregated/notes/p8/SV.md).
|
||||
//
|
||||
// Before P8-SV the wire arm read the three group counts through ReadWireBuffer and dispatched them
|
||||
// from the CPU, so a dispatch buffer a shader had written cost a whole-GPU wait per call - the
|
||||
// dispatch twin of what P8-D removed for indirect draws (WireIndirectDrawScenario.cpp). The cases
|
||||
// pin both halves:
|
||||
// * THE GRID, on every arm and both backends - each dispatch marks exactly the work groups its
|
||||
// counts name, and GPU-written counts are the ones the dispatch sees (the buffer starts with
|
||||
// OTHER counts, so a stale read shows as the wrong grid);
|
||||
// * THE SERVER'S OWN COUNTERS (Harness/WireIndirectPeek.h), where they can be read - Magma on the
|
||||
// inproc arm: every call issued as vkCmdDispatchIndirect (`wdsp`), no host wait (`whw`), and the
|
||||
// INDIRECT_COMMAND_READ barrier exactly once per shader write (`wibar`).
|
||||
// The counters are read BEFORE the grids are read back: that readback is a host read of a store a
|
||||
// shader wrote, so it takes a host wait of its own, which is the application's and not the
|
||||
// dispatch's.
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../Harness/HeadlessGL.h"
|
||||
#include "../Harness/ScenarioFixture.h"
|
||||
#include "../Harness/SplitRuntimePeek.h"
|
||||
#include "../Harness/WireIndirectPeek.h"
|
||||
|
||||
#ifdef GLAPI
|
||||
#undef GLAPI
|
||||
#endif
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
namespace MGITest {
|
||||
namespace {
|
||||
|
||||
// Grids up to 4 x 4 x 2: one cell per work group.
|
||||
constexpr std::uint32_t kGridX = 4, kGridY = 4, kGridZ = 2;
|
||||
constexpr std::uint32_t kCells = kGridX * kGridY * kGridZ;
|
||||
|
||||
// One invocation per work group, and each group marks its own cell.
|
||||
constexpr const char* kMarkSource = R"(#version 430 core
|
||||
layout(local_size_x = 1) in;
|
||||
layout(std430, binding = 2) buffer Marks { uint marks[]; };
|
||||
void main() {
|
||||
uvec3 g = gl_WorkGroupID;
|
||||
atomicAdd(marks[(g.z * 4u + g.y) * 4u + g.x], 1u);
|
||||
}
|
||||
)";
|
||||
// Copies `source` into `words`, one word per invocation: the compute write under test.
|
||||
constexpr const char* kCopySource = R"(#version 430 core
|
||||
layout(local_size_x = 1) in;
|
||||
layout(std430, binding = 0) buffer Words { uint words[]; };
|
||||
layout(std430, binding = 1) readonly buffer Source { uint source[]; };
|
||||
void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x]; }
|
||||
)";
|
||||
|
||||
using Grid = std::array<std::uint32_t, 3>;
|
||||
|
||||
class WireIndirectDispatchScenario : public ScenarioTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ScenarioTest::SetUp();
|
||||
if (!Ready()) return;
|
||||
m_magma = Gl().BackendName() == "DirectVulkan";
|
||||
m_transport = PeekSplitRuntime().transportName;
|
||||
m_mark = CompileCompute(kMarkSource);
|
||||
ASSERT_NE(m_mark, 0u);
|
||||
m_copy = CompileCompute(kCopySource);
|
||||
ASSERT_NE(m_copy, 0u);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "scene setup left a GL error behind";
|
||||
// The counters are armed by the first peek, so it has to come before any dispatch.
|
||||
if (CountersReadable()) {
|
||||
ASSERT_TRUE(PeekWireIndirectCounters(&m_before))
|
||||
<< "Magma on the inproc arm must be able to read the wire arm's own counters";
|
||||
}
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
if (!Ready()) return;
|
||||
glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, 0);
|
||||
for (GLuint& buffer : m_marks) {
|
||||
if (buffer != 0) glDeleteBuffers(1, &buffer);
|
||||
}
|
||||
for (GLuint* buffer : {&m_parameters, &m_source}) {
|
||||
if (*buffer != 0) glDeleteBuffers(1, buffer);
|
||||
*buffer = 0;
|
||||
}
|
||||
if (m_mark != 0) glDeleteProgram(m_mark);
|
||||
if (m_copy != 0) glDeleteProgram(m_copy);
|
||||
}
|
||||
|
||||
static GLuint CompileCompute(const char* source) {
|
||||
const GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
|
||||
glShaderSource(shader, 1, &source, nullptr);
|
||||
glCompileShader(shader);
|
||||
GLint ok = GL_FALSE;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
|
||||
if (ok != GL_TRUE) {
|
||||
glDeleteShader(shader);
|
||||
return 0;
|
||||
}
|
||||
const GLuint program = glCreateProgram();
|
||||
glAttachShader(program, shader);
|
||||
glLinkProgram(program);
|
||||
glDeleteShader(shader);
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &ok);
|
||||
if (ok != GL_TRUE) {
|
||||
glDeleteProgram(program);
|
||||
return 0;
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
static void Fill(GLuint& name, GLenum target, const std::vector<std::uint32_t>& words) {
|
||||
if (name == 0) glGenBuffers(1, &name);
|
||||
glBindBuffer(target, name);
|
||||
glBufferData(target, static_cast<GLsizeiptr>(words.size() * sizeof(std::uint32_t)), words.data(),
|
||||
GL_DYNAMIC_COPY);
|
||||
}
|
||||
|
||||
// The dispatch-indirect store, holding CPU-written counts.
|
||||
void Parameters(const std::vector<std::uint32_t>& words) {
|
||||
Fill(m_parameters, GL_DISPATCH_INDIRECT_BUFFER, words);
|
||||
}
|
||||
|
||||
// The compute write: the parameter store (already holding its poison) receives `words`.
|
||||
void ComputeWriteParameters(const std::vector<std::uint32_t>& words) {
|
||||
Fill(m_source, GL_SHADER_STORAGE_BUFFER, words);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_parameters);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_source);
|
||||
glUseProgram(m_copy);
|
||||
glDispatchCompute(static_cast<GLuint>(words.size()), 1, 1);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0);
|
||||
}
|
||||
|
||||
// One indirect dispatch of the mark program into a fresh, zeroed cell buffer. Returns
|
||||
// the index of that buffer for ExpectGrid.
|
||||
size_t DispatchIndirect(GLintptr offset) {
|
||||
GLuint marks = 0;
|
||||
Fill(marks, GL_SHADER_STORAGE_BUFFER, std::vector<std::uint32_t>(kCells, 0u));
|
||||
m_marks.push_back(marks);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, marks);
|
||||
glUseProgram(m_mark);
|
||||
glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, m_parameters);
|
||||
glDispatchComputeIndirect(offset);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0);
|
||||
return m_marks.size() - 1;
|
||||
}
|
||||
|
||||
// A round trip that reads no buffer store, so the server has applied every dispatch
|
||||
// above before the counters are read - and no host wait of its own is added.
|
||||
static void Settle() {
|
||||
BindDefaultFramebuffer();
|
||||
(void)ReadPixels(1, 1);
|
||||
}
|
||||
|
||||
void ExpectGrid(size_t which, const Grid& grid, const std::string& what) {
|
||||
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
std::vector<std::uint32_t> cells(kCells, 0xDEADu);
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_marks[which]);
|
||||
glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, static_cast<GLsizeiptr>(kCells * sizeof(std::uint32_t)),
|
||||
cells.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << what << ": readback";
|
||||
size_t wrong = 0;
|
||||
std::string first;
|
||||
for (std::uint32_t z = 0; z < kGridZ; ++z) {
|
||||
for (std::uint32_t y = 0; y < kGridY; ++y) {
|
||||
for (std::uint32_t x = 0; x < kGridX; ++x) {
|
||||
const bool inside = x < grid[0] && y < grid[1] && z < grid[2];
|
||||
const std::uint32_t got = cells[(z * kGridY + y) * kGridX + x];
|
||||
if (got != (inside ? 1u : 0u) && wrong++ == 0) {
|
||||
first = "group (" + std::to_string(x) + "," + std::to_string(y) + "," +
|
||||
std::to_string(z) + ") = " + std::to_string(got);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(wrong, 0u) << what << ": expected exactly the " << grid[0] << "x" << grid[1] << "x"
|
||||
<< grid[2] << " grid marked once; first wrong " << first;
|
||||
}
|
||||
|
||||
// Magma's server in THIS process: the inproc arm. Spawn and tcp servers are other
|
||||
// processes, and the monolith arm never takes the wire path at all.
|
||||
bool CountersReadable() const { return m_magma && m_transport == "inproc"; }
|
||||
|
||||
WireIndirectCounters Delta() const {
|
||||
WireIndirectCounters now{};
|
||||
EXPECT_TRUE(PeekWireIndirectCounters(&now));
|
||||
WireIndirectCounters delta{};
|
||||
delta.nativeDraws = now.nativeDraws - m_before.nativeDraws;
|
||||
delta.cpuExpansions = now.cpuExpansions - m_before.cpuExpansions;
|
||||
delta.barriers = now.barriers - m_before.barriers;
|
||||
delta.hostWaits = now.hostWaits - m_before.hostWaits;
|
||||
delta.indirectWaits = now.indirectWaits - m_before.indirectWaits;
|
||||
delta.nativeDispatches = now.nativeDispatches - m_before.nativeDispatches;
|
||||
return delta;
|
||||
}
|
||||
|
||||
bool m_magma = false;
|
||||
std::string m_transport;
|
||||
WireIndirectCounters m_before{};
|
||||
GLuint m_mark = 0, m_copy = 0, m_parameters = 0, m_source = 0;
|
||||
std::vector<GLuint> m_marks;
|
||||
};
|
||||
|
||||
// CPU-written counts at two non-zero offsets of one store. On Magma/inproc both calls must
|
||||
// be issued natively, with no host wait and no barrier (no shader wrote the store).
|
||||
TEST_F(WireIndirectDispatchScenario, CpuWrittenGroupCountsAreReadByTheGpuAtTheirOffsets) {
|
||||
if (!Ready() || IsSkipped()) return;
|
||||
Parameters({1, 1, 1, 3, 2, 1, 4, 1, 2});
|
||||
const size_t first = DispatchIndirect(12);
|
||||
const size_t second = DispatchIndirect(24);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
Settle();
|
||||
if (CountersReadable()) {
|
||||
const WireIndirectCounters delta = Delta();
|
||||
EXPECT_EQ(delta.nativeDispatches, 2u) << "every indirect dispatch must be vkCmdDispatchIndirect";
|
||||
EXPECT_EQ(delta.hostWaits, 0u) << "a host wait was taken with no GPU write to the counts";
|
||||
EXPECT_EQ(delta.barriers, 0u) << "no shader wrote this store, so no barrier is due";
|
||||
}
|
||||
ExpectGrid(first, {3, 2, 1}, "glDispatchComputeIndirect at byte 12");
|
||||
ExpectGrid(second, {4, 1, 2}, "glDispatchComputeIndirect at byte 24");
|
||||
}
|
||||
|
||||
// Counts a compute shader wrote, behind the application's own GL_COMMAND_BARRIER_BIT. The
|
||||
// store starts with OTHER counts, so a stale read dispatches the wrong grid. On
|
||||
// Magma/inproc: issued natively and - the point of the change - NO host wait.
|
||||
TEST_F(WireIndirectDispatchScenario, ComputeWrittenGroupCountsDispatchWithoutAHostWait) {
|
||||
if (!Ready() || IsSkipped()) return;
|
||||
Parameters({1, 1, 1});
|
||||
ComputeWriteParameters({3, 2, 1});
|
||||
glMemoryBarrier(GL_COMMAND_BARRIER_BIT);
|
||||
const size_t first = DispatchIndirect(0);
|
||||
|
||||
Parameters({1, 1, 1, 1, 1, 1});
|
||||
ComputeWriteParameters({1, 1, 1, 2, 2, 2});
|
||||
glMemoryBarrier(GL_COMMAND_BARRIER_BIT);
|
||||
const size_t second = DispatchIndirect(12);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
Settle();
|
||||
if (CountersReadable()) {
|
||||
const WireIndirectCounters delta = Delta();
|
||||
EXPECT_EQ(delta.nativeDispatches, 2u) << "both dispatches must be issued natively";
|
||||
EXPECT_EQ(delta.hostWaits, 0u)
|
||||
<< "an indirect dispatch of shader-written counts waited for the GPU on the host";
|
||||
EXPECT_EQ(delta.barriers, 2u) << "one INDIRECT_COMMAND_READ barrier per freshly written store";
|
||||
}
|
||||
ExpectGrid(first, {3, 2, 1}, "compute-written counts at byte 0");
|
||||
ExpectGrid(second, {2, 2, 2}, "compute-written counts at byte 12");
|
||||
}
|
||||
|
||||
// Flywheel's shape, one call over: the counts are written and dispatched with no
|
||||
// glMemoryBarrier between (GL leaves it undefined). The wire arm orders it anyway, once per
|
||||
// write, as P8-D does for draws. Magma's split arms only: the monolith arm records no
|
||||
// dependency here (P8-D recorded that half for dev), and Espryt's is its driver's.
|
||||
TEST_F(WireIndirectDispatchScenario, ShaderWrittenGroupCountsWithoutAnApplicationBarrierAreOrdered) {
|
||||
if (!Ready() || IsSkipped()) return;
|
||||
if (!m_magma || m_transport == "monolith") {
|
||||
GTEST_SKIP() << "the implicit INDIRECT_COMMAND_READ barrier is the Magma wire arm's (P8-D, P8-SV)";
|
||||
}
|
||||
Parameters({1, 1, 1});
|
||||
ComputeWriteParameters({3, 2, 1});
|
||||
const size_t first = DispatchIndirect(0);
|
||||
const size_t again = DispatchIndirect(0);
|
||||
ComputeWriteParameters({1, 2, 2});
|
||||
const size_t third = DispatchIndirect(0);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
Settle();
|
||||
if (CountersReadable()) {
|
||||
const WireIndirectCounters delta = Delta();
|
||||
EXPECT_EQ(delta.nativeDispatches, 3u);
|
||||
EXPECT_EQ(delta.hostWaits, 0u) << "a host wait was taken";
|
||||
EXPECT_EQ(delta.barriers, 2u) << "exactly one barrier per write: the second dispatch reads no new write";
|
||||
}
|
||||
ExpectGrid(first, {3, 2, 1}, "the first dispatch after the write");
|
||||
ExpectGrid(again, {3, 2, 1}, "a second dispatch, no write between");
|
||||
ExpectGrid(third, {1, 2, 2}, "the dispatch after a second write");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace MGITest
|
||||
@@ -0,0 +1,220 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Scenarios/WireMipShapeScenario.cpp
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
//
|
||||
// P8-SV: TWO glGenerateMipmap SHAPES THE MAGMA WIRE ARM GOT WRONG (docs/Disaggregated/notes/p8/SV.md).
|
||||
//
|
||||
// * A 3D chain through the NATIVE blit. The wire arm passed the level's depth as the blit's
|
||||
// subresource layerCount on an image with one array layer (VUID-vkCmdBlitImage-srcImage-00240,
|
||||
// -srcSubresource-01707, -dstSubresource-01708). Lavapipe blits a 3D image by its z offsets and
|
||||
// ignores layerCount, so the PIXELS below are right with or without the fix on this host; what
|
||||
// goes red on the host is the validation layer, which the `.Mip3DValidation.` entry runs this
|
||||
// case under (inproc, so the server's Vulkan instance is this process and its stdout is ours).
|
||||
// The pixel half runs on both backends and every arm, and is what a driver that honours
|
||||
// layerCount would fail.
|
||||
// * A 1D DEPTH chain with no native blit. The depth shader arm renders 2D views, which a 1D image
|
||||
// does not admit; the wire arm handed it the chain anyway. It is now the named decline
|
||||
// MipmapShaderFormatOrShape, decided before the chain grows, and the session carries on.
|
||||
// Magma only, and reachable on this host only with MGITEST_MAGMA_FORCE_SHADER_MIPMAP (lavapipe
|
||||
// blits depth natively), which the SERVER reads: split and spawn entries only.
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../Harness/HeadlessGL.h"
|
||||
#include "../Harness/ScenarioFixture.h"
|
||||
#include "../Harness/SplitLane.h"
|
||||
#include "../Harness/SplitRuntimePeek.h"
|
||||
#include "../Harness/WireDeclinePeek.h"
|
||||
|
||||
#ifdef GLAPI
|
||||
#undef GLAPI
|
||||
#endif
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
namespace MGITest {
|
||||
namespace {
|
||||
|
||||
using Texel = std::array<int, 4>;
|
||||
|
||||
// Is the Khronos validation layer loaded into THIS process? On the inproc arm the server's
|
||||
// Vulkan instance lives here, so this is the proof that a run "under the layer" really was.
|
||||
bool ValidationLayerMapped() {
|
||||
#if defined(__linux__)
|
||||
std::ifstream maps("/proc/self/maps");
|
||||
std::string line;
|
||||
while (std::getline(maps, line)) {
|
||||
if (line.find("VkLayer_khronos_validation") != std::string::npos) return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
class WireMipShapeScenario : public ScenarioTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ScenarioTest::SetUp();
|
||||
if (!Ready()) return;
|
||||
m_magma = Gl().BackendName() == "DirectVulkan";
|
||||
m_transport = PeekSplitRuntime().transportName;
|
||||
}
|
||||
|
||||
// The Magma server's decline tally is readable where the server is this process.
|
||||
bool DeclinesReadable() const { return m_magma && m_transport == "inproc"; }
|
||||
|
||||
bool m_magma = false;
|
||||
std::string m_transport;
|
||||
};
|
||||
|
||||
// A 3D RGBA8 chain, 8x8x8 with its full storage, whose level-0 slices come in PAIRS of one
|
||||
// colour: level 1's slice k is then pair k's colour exactly, and every level below is the
|
||||
// average of two slices of the level above. A blit that loses the z axis (one slice only,
|
||||
// or slices read as array layers of a 2D image) cannot produce those numbers.
|
||||
TEST_F(WireMipShapeScenario, VolumeChainIsGeneratedAlongItsDepth) {
|
||||
if (!Ready() || IsSkipped()) return;
|
||||
constexpr int kSide = 8, kLevels = 4;
|
||||
const std::array<Texel, 4> pairs = {Texel{200, 40, 80, 255}, Texel{40, 200, 120, 255},
|
||||
Texel{100, 100, 240, 255}, Texel{240, 160, 0, 255}};
|
||||
GLuint volume = 0;
|
||||
glGenTextures(1, &volume);
|
||||
glBindTexture(GL_TEXTURE_3D, volume);
|
||||
glTexStorage3D(GL_TEXTURE_3D, kLevels, GL_RGBA8, kSide, kSide, kSide);
|
||||
std::vector<GLubyte> level0(static_cast<size_t>(kSide * kSide * kSide * 4));
|
||||
for (int z = 0; z < kSide; ++z) {
|
||||
const Texel& texel = pairs[static_cast<size_t>(z / 2)];
|
||||
for (int at = 0; at < kSide * kSide; ++at) {
|
||||
for (int c = 0; c < 4; ++c)
|
||||
level0[static_cast<size_t>((z * kSide * kSide + at) * 4 + c)] = static_cast<GLubyte>(texel[c]);
|
||||
}
|
||||
}
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, kSide, kSide, kSide, GL_RGBA, GL_UNSIGNED_BYTE, level0.data());
|
||||
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "3D texture setup";
|
||||
glGenerateMipmap(GL_TEXTURE_3D);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "glGenerateMipmap(GL_TEXTURE_3D)";
|
||||
|
||||
const auto average = [](const Texel& a, const Texel& b) {
|
||||
return Texel{(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2, (a[3] + b[3]) / 2};
|
||||
};
|
||||
// expected[level][slice]
|
||||
std::vector<std::vector<Texel>> expected(kLevels);
|
||||
expected[1].assign(pairs.begin(), pairs.end());
|
||||
expected[2] = {average(pairs[0], pairs[1]), average(pairs[2], pairs[3])};
|
||||
expected[3] = {average(expected[2][0], expected[2][1])};
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
for (int level = 1; level < kLevels; ++level) {
|
||||
const int side = kSide >> level;
|
||||
GLint width = 0, height = 0, depth = 0;
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_3D, level, GL_TEXTURE_WIDTH, &width);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_3D, level, GL_TEXTURE_HEIGHT, &height);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_3D, level, GL_TEXTURE_DEPTH, &depth);
|
||||
EXPECT_EQ(width, side) << "level " << level;
|
||||
EXPECT_EQ(height, side) << "level " << level;
|
||||
EXPECT_EQ(depth, side) << "level " << level;
|
||||
std::vector<GLubyte> pixels(static_cast<size_t>(side * side * side * 4), 0x5a);
|
||||
glGetTexImage(GL_TEXTURE_3D, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "readback of level " << level;
|
||||
for (int z = 0; z < side; ++z) {
|
||||
const Texel& want = expected[static_cast<size_t>(level)][static_cast<size_t>(z)];
|
||||
size_t wrong = 0;
|
||||
Texel first{};
|
||||
for (int at = 0; at < side * side; ++at) {
|
||||
const size_t base = static_cast<size_t>((z * side * side + at) * 4);
|
||||
bool ok = true;
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
const int got = pixels[base + static_cast<size_t>(c)];
|
||||
if (got < want[c] - 2 || got > want[c] + 2) ok = false;
|
||||
}
|
||||
if (!ok && wrong++ == 0) {
|
||||
for (int c = 0; c < 4; ++c) first[c] = pixels[base + static_cast<size_t>(c)];
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(wrong, 0u) << "level " << level << " slice " << z << ": expected (" << want[0] << ","
|
||||
<< want[1] << "," << want[2] << "," << want[3] << "), first wrong texel ("
|
||||
<< first[0] << "," << first[1] << "," << first[2] << "," << first[3] << ")";
|
||||
}
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_3D, 0);
|
||||
glDeleteTextures(1, &volume);
|
||||
|
||||
// The `.Mip3DValidation.` entry: the blit above ran under the Khronos validation layer,
|
||||
// whose VUID lines the entry's FAIL_REGULAR_EXPRESSION reads. Without the layer actually
|
||||
// loaded, a clean output would prove nothing, so the entry requires it.
|
||||
if (SplitLane::MarkerIsOne("MGITEST_REQUIRE_VK_VALIDATION_LAYER")) {
|
||||
ASSERT_TRUE(m_magma && m_transport == "inproc")
|
||||
<< "the validation entry reads the server's layer output in-process: Magma, inproc";
|
||||
EXPECT_TRUE(ValidationLayerMapped())
|
||||
<< "VK_LAYER_KHRONOS_validation is not loaded in this process, so the absence of "
|
||||
"VUID lines in this entry's output says nothing";
|
||||
}
|
||||
}
|
||||
|
||||
// A 1D DEPTH chain with the native blit forced off: no mip arm renders a 1D image, so the
|
||||
// wire arm declines by name (MipmapShaderFormatOrShape) before growing the chain. Pinned:
|
||||
// no GL error, the session answering (two readback round trips), level 0 as uploaded, level
|
||||
// 1 still the application's own sentinel - and, where the server is this process, exactly
|
||||
// one MipmapShaderFormatOrShape decline counted.
|
||||
TEST_F(WireMipShapeScenario, Depth1DChainWithoutANativeBlitDeclinesByName) {
|
||||
if (!Ready() || IsSkipped()) return;
|
||||
if (!m_magma)
|
||||
GTEST_SKIP() << "Magma wire-arm decline (P8-SV): the knob and the depth shader arm it forces are "
|
||||
"Magma's; Espryt (" << Gl().BackendName() << ") generates this chain itself";
|
||||
const std::string tier = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_SHADER_MIPMAP");
|
||||
if (tier.empty())
|
||||
GTEST_SKIP() << "MGITEST_MAGMA_FORCE_SHADER_MIPMAP is unset: this driver blits depth natively, "
|
||||
"so the depth shader arm this case refuses is not entered";
|
||||
constexpr int kWidth = 16;
|
||||
GLuint line = 0;
|
||||
glGenTextures(1, &line);
|
||||
glBindTexture(GL_TEXTURE_1D, line);
|
||||
glTexStorage1D(GL_TEXTURE_1D, 5, GL_DEPTH_COMPONENT32F, kWidth);
|
||||
const std::vector<GLfloat> level0(kWidth, 0.25f), level1(kWidth / 2, 0.75f);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glTexSubImage1D(GL_TEXTURE_1D, 0, 0, kWidth, GL_DEPTH_COMPONENT, GL_FLOAT, level0.data());
|
||||
glTexSubImage1D(GL_TEXTURE_1D, 1, 0, kWidth / 2, GL_DEPTH_COMPONENT, GL_FLOAT, level1.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "1D depth texture setup";
|
||||
unsigned long long declinesBefore = 0;
|
||||
const bool counted =
|
||||
DeclinesReadable() && PeekWireDeclineCount("MipmapShaderFormatOrShape", &declinesBefore);
|
||||
glGenerateMipmap(GL_TEXTURE_1D);
|
||||
EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "glGenerateMipmap(GL_TEXTURE_1D) on a depth chain";
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
for (const int level : {0, 1}) {
|
||||
const int width = kWidth >> level;
|
||||
std::vector<GLfloat> depths(static_cast<size_t>(width), -1.0f);
|
||||
glGetTexImage(GL_TEXTURE_1D, level, GL_DEPTH_COMPONENT, GL_FLOAT, depths.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "readback of level " << level;
|
||||
const GLfloat want = level == 0 ? 0.25f : 0.75f;
|
||||
size_t wrong = 0;
|
||||
GLfloat first = 0.0f;
|
||||
for (const GLfloat depth : depths) {
|
||||
if (depth < want - 0.0001f || depth > want + 0.0001f) {
|
||||
if (wrong++ == 0) first = depth;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(wrong, 0u) << "level " << level << " tier=" << tier << ": a declined mip must leave "
|
||||
<< "every level as the application wrote it (expected " << want
|
||||
<< ", first wrong " << first << ")";
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_1D, 0);
|
||||
glDeleteTextures(1, &line);
|
||||
if (counted) {
|
||||
unsigned long long declinesAfter = 0;
|
||||
ASSERT_TRUE(PeekWireDeclineCount("MipmapShaderFormatOrShape", &declinesAfter));
|
||||
EXPECT_EQ(declinesAfter - declinesBefore, 1u)
|
||||
<< "the 1D depth chain must be refused once, by name, before the chain grows";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace MGITest
|
||||
@@ -64,7 +64,12 @@ FIXTURE_ENTRIES = {"TcpServer.Start", "TcpServer.Stop"}
|
||||
# answers unavailable, so a copy there checks pixels under a name claiming
|
||||
# the pools. The pixels do run on every arm: the Espryt entries and the
|
||||
# Magma informational and full-suite tiers.
|
||||
MAGMA_INPROC_ONLY = ("MagmaRunAheadScenario.", "MagmaWireCacheScenario.", "DescriptorPoolGrowthScenario.")
|
||||
# `.Mip3DValidation.` (P8-SV, a TAIL): WireMipShapeScenario's 3D chain under the Khronos validation
|
||||
# layer, red on a vkCmdBlitImage VUID in the entry's own output and proving the
|
||||
# layer loaded from /proc/self/maps - both need the server's Vulkan instance
|
||||
# in this process. The knob-free `.Mip3D.` entries keep all three arms.
|
||||
MAGMA_INPROC_ONLY = ("MagmaRunAheadScenario.", "MagmaWireCacheScenario.", "DescriptorPoolGrowthScenario.",
|
||||
".Mip3DValidation.")
|
||||
|
||||
# P7 wave 2-B2: THE SANCTIONED ASYMMETRY THAT IS NOT INPROC-ONLY - these exist on split AND
|
||||
# spawn and cannot exist on tcp. Every MGITEST_MAGMA_FORCE_* knob is read BY THE SERVER
|
||||
@@ -140,7 +145,9 @@ MAGMA_SERVER_ENV_KNOB_NO_TCP = (".ShaderMip1.", ".ShaderMip2.", ".DepthMip.",
|
||||
f".PrimGenReroute.PrimitivesGeneratedNoXfbScenario.{case}" for case in PRIMGEN_REROUTE_CASES) + tuple(
|
||||
f".SplitRecording64.OversizedRecordingSplitScenario.{case}" for case in SPLIT_RECORDING_CASES) + (
|
||||
# P8-F: the mip shader-shape decline, forced by MGITEST_MAGMA_FORCE_SHADER_MIPMAP (server-read).
|
||||
".ShaderMipDecline.",)
|
||||
".ShaderMipDecline.",
|
||||
# P8-SV: the 1D depth chain's decline, forced by the same server-read knob.
|
||||
".ShaderMipDepth1D.",)
|
||||
|
||||
|
||||
# THE MIRROR SHAPE - entries that exist on tcp ALONE, by construction. Each tail must match at least
|
||||
|
||||
Reference in New Issue
Block a user