mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Feat] (MG_Backend): wire primitive restart into both backends; detect dualSrcBlend
Make GL_PRIMITIVE_RESTART[_FIXED_INDEX] actually take effect at draw time, following the detect-at-init / POST / fallback-or-hard-fail discipline. DirectVulkan: - Thread primitiveRestartEnable through the pipeline (payload + hash + input-assembly), set from the GL_PRIMITIVE_RESTART / _FIXED_INDEX caps. - Detect and enable primitiveTopologyListRestart (VK_EXT_primitive_topology_list_restart) at device creation; cache it. Strip/fan restart needs no feature; a *list* topology with restart and no feature hard-fails at the draw with the reason. - Vulkan only restarts on the fixed all-ones index value, so an arbitrary GL_PRIMITIVE_RESTART index that is not that value hard-fails in UploadAndBindIndexBuffer (where the index type is known). - Also detect+enable and cache the dualSrcBlend base feature (groundwork for GL_SRC1_* dual-source blending). DirectGLES: - Sync GL_PRIMITIVE_RESTART_FIXED_INDEX from either restart cap (GLES core has only the fixed-index form); an arbitrary non-fixed index hard-fails in the indexed draw paths with the reason. POST: dualSrcBlend and primitiveTopologyListRestart capability rows (Pass when supported, Warn with the fallback/hard-fail consequence otherwise). Library builds clean; SanityTest 31/31. (The actual restart rendering and the hard-fail paths need a real GPU and are not runtime-testable here.)
This commit is contained in:
@@ -564,6 +564,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
#undef SYNC_CAPABILITY
|
||||
|
||||
{ // Primitive restart. GLES core has only GL_PRIMITIVE_RESTART_FIXED_INDEX (fixed all-ones
|
||||
// value); both the fixed cap and the (fixed-valued) arbitrary GL_PRIMITIVE_RESTART map to
|
||||
// it. An arbitrary non-fixed restart index is rejected at draw time (see DrawElements).
|
||||
const Bool restart = parameters.PrimitiveRestartFixedIndexEnabled || parameters.PrimitiveRestartEnabled;
|
||||
const Bool syncedRestart = g_syncedRenderStateParameters.PrimitiveRestartFixedIndexEnabled ||
|
||||
g_syncedRenderStateParameters.PrimitiveRestartEnabled;
|
||||
if (restart != syncedRestart) {
|
||||
restart ? g_GLESFuncs.glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX)
|
||||
: g_GLESFuncs.glDisable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
|
||||
}
|
||||
}
|
||||
|
||||
const auto& ToGLBoolean = [](Bool b) -> GLboolean { return b ? GL_TRUE : GL_FALSE; };
|
||||
|
||||
{ // Blend State
|
||||
@@ -1329,12 +1341,38 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_GLESFuncs.glClear(mask);
|
||||
}
|
||||
|
||||
// GLES core supports only GL_PRIMITIVE_RESTART_FIXED_INDEX (fixed all-ones value). If the app
|
||||
// enabled the arbitrary GL_PRIMITIVE_RESTART with a non-fixed index, hard-fail at this draw with
|
||||
// the reason (a fallback would silently drop restarts and corrupt geometry).
|
||||
void CheckPrimitiveRestartSupported(GLenum indexType) {
|
||||
if (!MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PrimitiveRestart) ||
|
||||
MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PrimitiveRestartFixedIndex)) {
|
||||
return;
|
||||
}
|
||||
Uint32 fixedMax = 0;
|
||||
switch (indexType) {
|
||||
case GL_UNSIGNED_BYTE: fixedMax = 0xFFu; break;
|
||||
case GL_UNSIGNED_SHORT: fixedMax = 0xFFFFu; break;
|
||||
case GL_UNSIGNED_INT: fixedMax = 0xFFFFFFFFu; break;
|
||||
default: return;
|
||||
}
|
||||
const Uint32 restartIndex = MG_State::pGLContext->GetPrimitiveRestartIndex();
|
||||
if (restartIndex != fixedMax) {
|
||||
THROW_EXCEPTION("GL_PRIMITIVE_RESTART with an arbitrary restart index (" + std::to_string(restartIndex) +
|
||||
") is not supported by the GLES backend, which only restarts on the fixed index value (" +
|
||||
std::to_string(fixedMax) +
|
||||
") for this index type; use GL_PRIMITIVE_RESTART_FIXED_INDEX or set glPrimitiveRestartIndex "
|
||||
"to that value.");
|
||||
}
|
||||
}
|
||||
|
||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
|
||||
DebugImpl::OpenGLScopeMarker marker(__func__);
|
||||
#endif
|
||||
DrawSyncBit syncBit = DrawSyncBit::IndexBuffer;
|
||||
PrepareForDraw(syncBit);
|
||||
CheckPrimitiveRestartSupported(type);
|
||||
g_GLESFuncs.glDrawElements(mode, count, type, indices);
|
||||
}
|
||||
|
||||
@@ -1360,6 +1398,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#endif
|
||||
DrawSyncBit syncBit = DrawSyncBit::IndexBuffer;
|
||||
PrepareForDraw(syncBit);
|
||||
CheckPrimitiveRestartSupported(type);
|
||||
g_GLESFuncs.glDrawElementsBaseVertex(mode, count, type, indices, basevertex);
|
||||
}
|
||||
|
||||
@@ -1390,6 +1429,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#endif
|
||||
DrawSyncBit syncBit = DrawSyncBit::IndexBuffer;
|
||||
PrepareForDraw(syncBit);
|
||||
CheckPrimitiveRestartSupported(type);
|
||||
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
g_GLESFuncs.glDrawElements(mode, count[i], type, indices[i]);
|
||||
@@ -1403,6 +1443,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#endif
|
||||
DrawSyncBit syncBit = DrawSyncBit::IndexBuffer;
|
||||
PrepareForDraw(syncBit);
|
||||
CheckPrimitiveRestartSupported(type);
|
||||
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
g_GLESFuncs.glDrawElementsBaseVertex(mode, count[i], type, indices[i], basevertex[i]);
|
||||
|
||||
@@ -126,6 +126,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.rasterizationSamples, sizeof(payload.rasterizationSamples)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.subpass, sizeof(payload.subpass)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.polygonMode, sizeof(payload.polygonMode)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.cullMode, sizeof(payload.cullMode)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontFace, sizeof(payload.frontFace)));
|
||||
@@ -212,6 +214,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
||||
ia.topology = payload.topology;
|
||||
ia.primitiveRestartEnable = payload.primitiveRestartEnable ? VK_TRUE : VK_FALSE;
|
||||
|
||||
VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
||||
vpci.viewportCount = 1;
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkSampleCountFlagBits rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
Uint32 subpass = 0;
|
||||
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
Bool primitiveRestartEnable = false;
|
||||
VkPolygonMode polygonMode = VK_POLYGON_MODE_FILL;
|
||||
VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT;
|
||||
VkFrontFace frontFace = VK_FRONT_FACE_CLOCKWISE;
|
||||
|
||||
@@ -2266,6 +2266,31 @@ void main() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// GL_PRIMITIVE_RESTART uses an arbitrary restart index (glPrimitiveRestartIndex), but Vulkan
|
||||
// only restarts on the fixed all-ones value of the index type. GL_PRIMITIVE_RESTART_FIXED_INDEX
|
||||
// already matches that, so only the arbitrary form needs checking; hard-fail at this draw with
|
||||
// the reason if the index is not the fixed value (a fallback would silently drop restarts).
|
||||
if (MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PrimitiveRestart) &&
|
||||
!MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PrimitiveRestartFixedIndex)) {
|
||||
const Uint32 restartIndex = MG_State::pGLContext->GetPrimitiveRestartIndex();
|
||||
Uint32 fixedMax = 0;
|
||||
switch (vkIndexType) {
|
||||
case VK_INDEX_TYPE_UINT8: fixedMax = 0xFFu; break;
|
||||
case VK_INDEX_TYPE_UINT16: fixedMax = 0xFFFFu; break;
|
||||
case VK_INDEX_TYPE_UINT32: fixedMax = 0xFFFFFFFFu; break;
|
||||
default: break;
|
||||
}
|
||||
if (restartIndex != fixedMax) {
|
||||
THROW_EXCEPTION("GL_PRIMITIVE_RESTART with an arbitrary restart index (" +
|
||||
std::to_string(restartIndex) +
|
||||
") is not supported by the Vulkan backend, which only restarts on the fixed index "
|
||||
"value (" +
|
||||
std::to_string(fixedMax) +
|
||||
") for this index type; use GL_PRIMITIVE_RESTART_FIXED_INDEX, or set "
|
||||
"glPrimitiveRestartIndex to that value.");
|
||||
}
|
||||
}
|
||||
|
||||
const auto* indexBuffer = vao.GetIndexBufferBindingSlot().GetBoundObject().get();
|
||||
MOBILEGL_ASSERT(indexBuffer != nullptr, "UploadAndBindIndexBuffer requires bound EBO");
|
||||
const SizeT indexDataSizeBytes = pIndexBufferView->indexByteSize;
|
||||
@@ -2973,6 +2998,25 @@ void main() {
|
||||
? requestedPolygonMode
|
||||
: VK_POLYGON_MODE_FILL;
|
||||
|
||||
const VkPrimitiveTopology vkTopology = MG_Util::ConvertPrimitiveModeToVkEnum(mode);
|
||||
const Bool primitiveRestartEnabled =
|
||||
MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PrimitiveRestart) ||
|
||||
MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PrimitiveRestartFixedIndex);
|
||||
// Primitive restart on a *list* topology requires the primitiveTopologyListRestart feature;
|
||||
// strip/fan restart works without it. Silently dropping restarts would corrupt geometry, so
|
||||
// hard-fail here (at the draw) with the reason when the device lacks the feature.
|
||||
const auto isListTopology = [](VkPrimitiveTopology t) {
|
||||
return t == VK_PRIMITIVE_TOPOLOGY_POINT_LIST || t == VK_PRIMITIVE_TOPOLOGY_LINE_LIST ||
|
||||
t == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST ||
|
||||
t == VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY ||
|
||||
t == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY || t == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
|
||||
};
|
||||
if (primitiveRestartEnabled && !m_primitiveTopologyListRestartFeatureEnabled && isListTopology(vkTopology)) {
|
||||
THROW_EXCEPTION("Primitive restart on a list topology requires the primitiveTopologyListRestart device "
|
||||
"feature (VK_EXT_primitive_topology_list_restart), which this device does not support; use "
|
||||
"a strip/fan topology or a device that supports it.");
|
||||
}
|
||||
|
||||
PipelineFactory::PipelineCreatePayload payload {
|
||||
.programHash = programObj.hash,
|
||||
.vertexInputHash = vertexInputHash,
|
||||
@@ -2981,7 +3025,8 @@ void main() {
|
||||
.colorAttachmentCount = renderPassEntry.colorAttachmentCount,
|
||||
.rasterizationSamples = renderPassEntry.sampleCount,
|
||||
.subpass = 0,
|
||||
.topology = MG_Util::ConvertPrimitiveModeToVkEnum(mode),
|
||||
.topology = vkTopology,
|
||||
.primitiveRestartEnable = primitiveRestartEnabled,
|
||||
.polygonMode = effectivePolygonMode,
|
||||
.cullMode = cullFaceEnabled
|
||||
? MG_Util::ConvertCullFaceModeToVkEnum(MG_State::pGLContext->GetCullFaceMode(), invertClockwise)
|
||||
@@ -6328,6 +6373,8 @@ void main() {
|
||||
m_independentBlendFeatureEnabled = deviceFeatures.independentBlend == VK_TRUE;
|
||||
deviceFeatures.fillModeNonSolid = supportedDeviceFeatures.fillModeNonSolid;
|
||||
m_fillModeNonSolidFeatureEnabled = deviceFeatures.fillModeNonSolid == VK_TRUE;
|
||||
deviceFeatures.dualSrcBlend = supportedDeviceFeatures.dualSrcBlend;
|
||||
m_dualSrcBlendFeatureEnabled = deviceFeatures.dualSrcBlend == VK_TRUE;
|
||||
deviceFeatures.logicOp = supportedDeviceFeatures.logicOp;
|
||||
deviceFeatures.shaderClipDistance = supportedDeviceFeatures.shaderClipDistance;
|
||||
deviceFeatures.shaderCullDistance = supportedDeviceFeatures.shaderCullDistance;
|
||||
@@ -6423,6 +6470,30 @@ void main() {
|
||||
MGLOG_W("shaderDrawParameters is unavailable; shaders using gl_DrawID/gl_BaseInstance will not work");
|
||||
}
|
||||
|
||||
// primitiveTopologyListRestart lets primitive restart work on *list* topologies (strip/fan
|
||||
// restart needs no feature). Optional; enabled via VK_EXT_primitive_topology_list_restart.
|
||||
m_primitiveTopologyListRestartFeatureEnabled = false;
|
||||
VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT listRestartFeatures{};
|
||||
listRestartFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT;
|
||||
if (IsExtensionSupported(availableExtensions, VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME) &&
|
||||
getPhysicalDeviceFeatures2 != nullptr) {
|
||||
VkPhysicalDeviceFeatures2 featureQuery{};
|
||||
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||
featureQuery.pNext = &listRestartFeatures;
|
||||
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
||||
if (listRestartFeatures.primitiveTopologyListRestart == VK_TRUE) {
|
||||
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions,
|
||||
VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME)) {
|
||||
enabledDeviceExtensions.push_back(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME);
|
||||
}
|
||||
listRestartFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
||||
deviceCreateInfo.pNext = &listRestartFeatures;
|
||||
m_primitiveTopologyListRestartFeatureEnabled = true;
|
||||
MGLOG_I("Enabled optional device extension: %s",
|
||||
VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
deviceCreateInfo.enabledExtensionCount = static_cast<Uint32>(enabledDeviceExtensions.size());
|
||||
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
|
||||
MGLOG_I("Device feature support: geometryShader=%s independentBlend=%s logicOp=%s shaderClipDistance=%s "
|
||||
|
||||
@@ -354,6 +354,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// drive a runtime fallback when the device lacks them.
|
||||
Bool m_fillModeNonSolidFeatureEnabled = false;
|
||||
Bool m_independentBlendFeatureEnabled = false;
|
||||
// dualSrcBlend gates GL_SRC1_* blend factors (glBindFragDataLocationIndexed dual-source blend);
|
||||
// primitiveTopologyListRestart gates primitive restart on *list* topologies (strip/fan restart
|
||||
// needs no feature). Both cached at device creation and drive a hard-fail-at-draw when absent.
|
||||
Bool m_dualSrcBlendFeatureEnabled = false;
|
||||
Bool m_primitiveTopologyListRestartFeatureEnabled = false;
|
||||
// Cached at device creation from the graphics queue family properties
|
||||
// and device limits; drives timer-query support.
|
||||
Uint32 m_timestampValidBits = 0;
|
||||
|
||||
@@ -1126,6 +1126,11 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
builder.Warn("independentBlend",
|
||||
"unsupported; per-draw-buffer glColorMaski falls back to draw buffer 0 for all attachments");
|
||||
}
|
||||
if (features.dualSrcBlend == VK_TRUE) {
|
||||
builder.Pass("dualSrcBlend", "GL_SRC1_* dual-source blend factors supported");
|
||||
} else {
|
||||
builder.Warn("dualSrcBlend", "unsupported; GL_SRC1_* dual-source blend factors hard-fail at draw");
|
||||
}
|
||||
|
||||
Bool shaderDrawParameters = false;
|
||||
if (vkGetPhysicalDeviceFeatures2Fn != nullptr && properties.apiVersion >= VK_API_VERSION_1_1) {
|
||||
@@ -1147,6 +1152,26 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
"unavailable; shaders using gl_DrawID/gl_BaseInstance will not work");
|
||||
}
|
||||
|
||||
Bool primitiveTopologyListRestart = false;
|
||||
if (vkGetPhysicalDeviceFeatures2Fn != nullptr &&
|
||||
HasVkExtension(deviceExtensions, VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME)) {
|
||||
VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT listRestartFeatures{};
|
||||
listRestartFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT;
|
||||
VkPhysicalDeviceFeatures2 features2{};
|
||||
features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||
features2.pNext = &listRestartFeatures;
|
||||
vkGetPhysicalDeviceFeatures2Fn(physicalDevice, &features2);
|
||||
primitiveTopologyListRestart = listRestartFeatures.primitiveTopologyListRestart == VK_TRUE;
|
||||
}
|
||||
if (primitiveTopologyListRestart) {
|
||||
builder.Pass("primitiveTopologyListRestart",
|
||||
"primitive restart supported on list topologies (GL_PRIMITIVE_RESTART)");
|
||||
} else {
|
||||
builder.Warn("primitiveTopologyListRestart",
|
||||
"unsupported; primitive restart works on strip/fan topologies only, list-topology restart "
|
||||
"hard-fails at draw");
|
||||
}
|
||||
|
||||
if (vkGetPhysicalDeviceProperties2Fn != nullptr && properties.apiVersion >= VK_API_VERSION_1_1) {
|
||||
VkPhysicalDeviceSubgroupProperties subgroupProperties{};
|
||||
subgroupProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES;
|
||||
|
||||
Reference in New Issue
Block a user