glsl: add nir version of validate_geometry_shader_emissions()

We will use this to replace the GLSL IR version.

Reviewed-by: Alejandro Piñeiro <apinheiro@igalia.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/27377>
This commit is contained in:
Timothy Arceri 2024-01-31 12:13:00 +11:00 committed by Marge Bot
parent cdfbd3323d
commit f21e2e6229

View file

@ -127,6 +127,116 @@ gl_nir_opts(nir_shader *nir)
NIR_PASS(_, nir, nir_lower_var_copies);
}
struct emit_vertex_state {
int max_stream_allowed;
int invalid_stream_id;
bool invalid_stream_id_from_emit_vertex;
bool end_primitive_found;
unsigned used_streams;
};
/**
* Determine the highest stream id to which a (geometry) shader emits
* vertices. Also check whether End{Stream}Primitive is ever called.
*/
static void
find_emit_vertex(struct emit_vertex_state *state, nir_shader *shader) {
nir_function_impl *impl = nir_shader_get_entrypoint(shader);
nir_foreach_block_safe(block, impl) {
nir_foreach_instr_safe(instr, block) {
if (instr->type == nir_instr_type_intrinsic) {
nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
if (intr->intrinsic == nir_intrinsic_emit_vertex ||
intr->intrinsic == nir_intrinsic_end_primitive) {
int stream_id = nir_intrinsic_stream_id(intr);
bool from_emit_vertex =
intr->intrinsic == nir_intrinsic_emit_vertex;
state->end_primitive_found |=
intr->intrinsic == nir_intrinsic_end_primitive;
if (stream_id < 0) {
state->invalid_stream_id = stream_id;
state->invalid_stream_id_from_emit_vertex = from_emit_vertex;
return;
}
if (stream_id > state->max_stream_allowed) {
state->invalid_stream_id = stream_id;
state->invalid_stream_id_from_emit_vertex = from_emit_vertex;
return;
}
state->used_streams |= 1 << stream_id;
}
}
}
}
}
/**
* Check if geometry shaders emit to non-zero streams and do corresponding
* validations.
*/
static void
validate_geometry_shader_emissions(const struct gl_constants *consts,
struct gl_shader_program *prog)
{
struct gl_linked_shader *sh = prog->_LinkedShaders[MESA_SHADER_GEOMETRY];
if (sh != NULL) {
struct emit_vertex_state state;
state.max_stream_allowed = consts->MaxVertexStreams - 1;
state.invalid_stream_id = 0;
state.invalid_stream_id_from_emit_vertex = false;
state.end_primitive_found = false;
state.used_streams = 0;
find_emit_vertex(&state, sh->Program->nir);
if (state.invalid_stream_id != 0) {
linker_error(prog, "Invalid call %s(%d). Accepted values for the "
"stream parameter are in the range [0, %d].\n",
state.invalid_stream_id_from_emit_vertex ?
"EmitStreamVertex" : "EndStreamPrimitive",
state.invalid_stream_id, state.max_stream_allowed);
}
prog->Geom.ActiveStreamMask = state.used_streams;
prog->Geom.UsesEndPrimitive = state.end_primitive_found;
/* From the ARB_gpu_shader5 spec:
*
* "Multiple vertex streams are supported only if the output primitive
* type is declared to be "points". A program will fail to link if it
* contains a geometry shader calling EmitStreamVertex() or
* EndStreamPrimitive() if its output primitive type is not "points".
*
* However, in the same spec:
*
* "The function EmitVertex() is equivalent to calling EmitStreamVertex()
* with <stream> set to zero."
*
* And:
*
* "The function EndPrimitive() is equivalent to calling
* EndStreamPrimitive() with <stream> set to zero."
*
* Since we can call EmitVertex() and EndPrimitive() when we output
* primitives other than points, calling EmitStreamVertex(0) or
* EmitEndPrimitive(0) should not produce errors. This it also what Nvidia
* does. We can use prog->Geom.ActiveStreamMask to check whether only the
* first (zero) stream is active.
* stream.
*/
if (prog->Geom.ActiveStreamMask & ~(1 << 0) &&
sh->Program->info.gs.output_primitive != MESA_PRIM_POINTS) {
linker_error(prog, "EmitStreamVertex(n) and EndStreamPrimitive(n) "
"with n>0 requires point output\n");
}
}
}
bool
gl_nir_can_add_pointsize_to_program(const struct gl_constants *consts,
struct gl_program *prog)