glsl: Validate vertex emission in geometry shaders.
authorIago Toral Quiroga <itoral@igalia.com>
Mon, 16 Jun 2014 14:09:53 +0000 (16:09 +0200)
committerIago Toral Quiroga <itoral@igalia.com>
Mon, 30 Jun 2014 06:08:50 +0000 (08:08 +0200)
Check if non-zero streams are used. Fail to link if emitting to unsupported
streams or emitting to non-zero streams with output type other than GL_POINTS.

Reviewed-by: Chris Forbes <chrisf@ijw.co.nz>
src/glsl/linker.cpp

index 247c828d3d695a78d9f0de0daf0a293fd84f963b..3036ebcb33e11d967283e8d809e933b3ccca1998 100644 (file)
@@ -250,31 +250,100 @@ public:
    }
 };
 
-
 /**
- * Visitor that determines whether or not a shader uses ir_end_primitive.
+ * Visitor that determines the highest stream id to which a (geometry) shader
+ * emits vertices. It also checks whether End{Stream}Primitive is ever called.
  */
-class find_end_primitive_visitor : public ir_hierarchical_visitor {
+class find_emit_vertex_visitor : public ir_hierarchical_visitor {
 public:
-   find_end_primitive_visitor()
-      : found(false)
+   find_emit_vertex_visitor(int max_allowed)
+      : max_stream_allowed(max_allowed),
+        invalid_stream_id(0),
+        invalid_stream_id_from_emit_vertex(false),
+        end_primitive_found(false),
+        uses_non_zero_stream(false)
    {
       /* empty */
    }
 
-   virtual ir_visitor_status visit(ir_end_primitive *)
+   virtual ir_visitor_status visit_leave(ir_emit_vertex *ir)
    {
-      found = true;
-      return visit_stop;
+      int stream_id = ir->stream_id();
+
+      if (stream_id < 0) {
+         invalid_stream_id = stream_id;
+         invalid_stream_id_from_emit_vertex = true;
+         return visit_stop;
+      }
+
+      if (stream_id > max_stream_allowed) {
+         invalid_stream_id = stream_id;
+         invalid_stream_id_from_emit_vertex = true;
+         return visit_stop;
+      }
+
+      if (stream_id != 0)
+         uses_non_zero_stream = true;
+
+      return visit_continue;
    }
 
-   bool end_primitive_found()
+   virtual ir_visitor_status visit_leave(ir_end_primitive *ir)
    {
-      return found;
+      end_primitive_found = true;
+
+      int stream_id = ir->stream_id();
+
+      if (stream_id < 0) {
+         invalid_stream_id = stream_id;
+         invalid_stream_id_from_emit_vertex = false;
+         return visit_stop;
+      }
+
+      if (stream_id > max_stream_allowed) {
+         invalid_stream_id = stream_id;
+         invalid_stream_id_from_emit_vertex = false;
+         return visit_stop;
+      }
+
+      if (stream_id != 0)
+         uses_non_zero_stream = true;
+
+      return visit_continue;
+   }
+
+   bool error()
+   {
+      return invalid_stream_id != 0;
+   }
+
+   const char *error_func()
+   {
+      return invalid_stream_id_from_emit_vertex ?
+         "EmitStreamVertex" : "EndStreamPrimitive";
+   }
+
+   int error_stream()
+   {
+      return invalid_stream_id;
+   }
+
+   bool uses_streams()
+   {
+      return uses_non_zero_stream;
+   }
+
+   bool uses_end_primitive()
+   {
+      return end_primitive_found;
    }
 
 private:
-   bool found;
+   int max_stream_allowed;
+   int invalid_stream_id;
+   bool invalid_stream_id_from_emit_vertex;
+   bool end_primitive_found;
+   bool uses_non_zero_stream;
 };
 
 } /* anonymous namespace */
@@ -551,10 +620,58 @@ validate_geometry_shader_executable(struct gl_shader_program *prog,
 
    analyze_clip_usage(prog, shader, &prog->Geom.UsesClipDistance,
                       &prog->Geom.ClipDistanceArraySize);
+}
+
+/**
+ * Check if geometry shaders emit to non-zero streams and do corresponding
+ * validations.
+ */
+static void
+validate_geometry_shader_emissions(struct gl_context *ctx,
+                                   struct gl_shader_program *prog)
+{
+   if (prog->_LinkedShaders[MESA_SHADER_GEOMETRY] != NULL) {
+      find_emit_vertex_visitor emit_vertex(ctx->Const.MaxVertexStreams - 1);
+      emit_vertex.run(prog->_LinkedShaders[MESA_SHADER_GEOMETRY]->ir);
+      if (emit_vertex.error()) {
+         linker_error(prog, "Invalid call %s(%d). Accepted values for the "
+                      "stream parameter are in the range [0, %d].",
+                      emit_vertex.error_func(),
+                      emit_vertex.error_stream(),
+                      ctx->Const.MaxVertexStreams - 1);
+      }
+      prog->Geom.UsesStreams = emit_vertex.uses_streams();
+      prog->Geom.UsesEndPrimitive = emit_vertex.uses_end_primitive();
 
-   find_end_primitive_visitor end_primitive;
-   end_primitive.run(shader->ir);
-   prog->Geom.UsesEndPrimitive = end_primitive.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. Currently we only set prog->Geom.UsesStreams to TRUE when
+       * EmitStreamVertex() or EmitEndPrimitive() are called with a non-zero
+       * stream.
+       */
+      if (prog->Geom.UsesStreams && prog->Geom.OutputType != GL_POINTS) {
+         linker_error(prog, "EmitStreamVertex(n) and EndStreamPrimitive(n) "
+                      "with n>0 requires point output");
+      }
+   }
 }
 
 
@@ -2558,6 +2675,9 @@ link_shaders(struct gl_context *ctx, struct gl_shader_program *prog)
         ;
    }
 
+   /* Check and validate stream emissions in geometry shaders */
+   validate_geometry_shader_emissions(ctx, prog);
+
    /* Mark all generic shader inputs and outputs as unpaired. */
    for (unsigned i = MESA_SHADER_VERTEX; i <= MESA_SHADER_FRAGMENT; i++) {
       if (prog->_LinkedShaders[i] != NULL) {