{
  "presentation": {
    "slides": [
      {
        "elements": [
          {
            "x": 1,
            "y": 1,
            "width": 98,
            "height": 97,
            "action": {
              "library": "H5P.AdvancedText 1.1",
              "params": {
                "text": "<div style=\"text-align:center;padding:1em;\"><h1 style=\"color:#1a237e;margin-bottom:0.4em;\">Compute Shaders in OpenGL</h1><p style=\"color:#555;font-size:0.95em;\">This lab has <strong>6</strong> implementation steps.</p><p style=\"color:#555;font-size:0.9em;\">Work through each step in Visual Studio, implementing the code as described. Use the arrow keys or the slide panel to navigate freely between steps.</p><p style=\"margin-top:1em;font-size:0.85em;color:#777;\">&rarr; Press the right arrow or click Next to begin.</p></div>"
              },
              "subContentId": "aaaaaaaa-0002-4000-8000-000000000000",
              "metadata": {
                "contentType": "Text",
                "license": "U",
                "title": "Untitled Text",
                "authors": [],
                "changes": []
              }
            },
            "alwaysDisplayComments": false,
            "backgroundOpacity": 0,
            "displayAsButton": false,
            "buttonSize": "big",
            "goToSlideType": "specified",
            "invisible": false,
            "solution": ""
          }
        ],
        "slideBackgroundSelector": {},
        "keywords": [
          {
            "main": "Overview"
          }
        ]
      },
      {
        "elements": [
          {
            "x": 1,
            "y": 1,
            "width": 98,
            "height": 97,
            "action": {
              "library": "H5P.AdvancedText 1.1",
              "params": {
                "text": "<h2 style=\"margin:0 0 0.5em 0;font-size:1.1em;color:#1a237e;\">Step 1: Inheriting from Shader</h2><div style=\"display:grid;grid-template-columns:1fr 1fr;gap:1em;\"><div style=\"background:#f3f4f6;padding:0.8em;border-radius:4px;border-left:4px solid #1565c0;\"><p style=\"margin:0 0 0.4em 0;font-size:0.8em;font-weight:bold;color:#1565c0;\">CONCEPT</p><div style=\"font-size:0.85em;\"><p>A compute shader in OpenGL is still just a <strong>program object</strong> — the same <code>GLuint</code> handle, the same <code>glUseProgram</code> binding, and the same <code>glUniform*</code> uniform system as any other shader. The only things that differ are how it is <em>compiled</em> (one stage, not two) and how it is <em>executed</em> (dispatched, not drawn).</p><p>Because all the shared infrastructure already lives in the base <code>Shader</code> class — <code>Bind()</code>, <code>Unbind()</code>, the uniform location cache, and the destructor — there is no reason to duplicate it. Public inheritance lets <code>ComputeShader</code> reuse all of that with zero changes.</p><p>The key design decision is the <strong>protected default constructor</strong> <code>Shader()</code>. Calling it sets <code>m_RendererID = 0</code> without triggering the vertex+fragment parsing path in the normal constructor. This gives <code>ComputeShader</code> a clean slate to perform its own single-stage compilation, while still owning the base class fields it needs.</p><p>Consider: what would go wrong if you called the public <code>Shader(vertPath, fragPath)</code> constructor instead?</p></div></div><div><div style=\"background:#fff8e1;padding:0.8em;border-radius:4px;border-left:4px solid #f57f17;\"><p style=\"margin:0 0 0.4em 0;font-size:0.8em;font-weight:bold;color:#e65100;\">YOUR TASK</p><div style=\"font-size:0.85em;\"><ol><li>Open <code>ComputeShader.h</code>.</li><li>Declare the class so that it inherits publicly from <code>Shader</code>.</li><li>Add a public constructor that accepts a <code>const std::string&amp; filepath</code>.</li><li>Add a public <code>Dispatch</code> method with signature:<br><code>void Dispatch(unsigned int groupsX, unsigned int groupsY, unsigned int groupsZ) const;</code></li><li>Add two private helpers: <code>ReadFile</code> and <code>Compile</code> with the signatures shown in the starter code.</li><li>Add the include guard and include <code>Shader.h</code>.</li></ol></div></div><div style=\"margin-top:0.6em;\"><p style=\"margin:0 0 0.3em 0;font-size:0.8em;color:#555;font-weight:bold;\">STARTER CODE</p><pre style=\"background:#1e1e1e;color:#d4d4d4;padding:0.8em;border-radius:4px;font-size:0.75em;overflow-x:auto;white-space:pre-wrap;\">#pragma once\n#include \"Shader.h\"\n\n// TODO: Declare ComputeShader inheriting from Shader.\n//       Add the constructor, Dispatch method, and two private helpers.\n//       Use the protected Shader() default constructor in the initialiser list.\nclass ComputeShader // TODO: inherit from Shader\n{\npublic:\n    // TODO: constructor\n    // TODO: Dispatch()\nprivate:\n    // TODO: ReadFile()\n    // TODO: Compile()\n};</pre></div><div style=\"margin-top:0.8em;padding:0.5em 0.8em;background:#e8f5e9;border-left:4px solid #4caf50;border-radius:2px;\"><p style=\"margin:0 0 0.2em 0;font-size:0.8em;font-weight:bold;color:#2e7d32;\">EXPECTED RESULT</p><p style=\"margin:0;font-size:0.85em;\">The header compiles with no errors. No functionality exists yet — that comes in later steps.</p></div></div></div>"
              },
              "subContentId": "aaaaaaaa-0003-4000-8000-000000000000",
              "metadata": {
                "contentType": "Text",
                "license": "U",
                "title": "Untitled Text",
                "authors": [],
                "changes": []
              }
            },
            "alwaysDisplayComments": false,
            "backgroundOpacity": 0,
            "displayAsButton": false,
            "buttonSize": "big",
            "goToSlideType": "specified",
            "invisible": false,
            "solution": ""
          }
        ],
        "slideBackgroundSelector": {},
        "keywords": [
          {
            "main": "Step 1 — Class Inheritance"
          }
        ]
      },
      {
        "elements": [
          {
            "x": 1,
            "y": 1,
            "width": 98,
            "height": 97,
            "action": {
              "library": "H5P.AdvancedText 1.1",
              "params": {
                "text": "<h2 style=\"margin:0 0 0.5em 0;font-size:1.1em;color:#1a237e;\">Step 2: Reading the Shader Source File</h2><div style=\"display:grid;grid-template-columns:1fr 1fr;gap:1em;\"><div style=\"background:#f3f4f6;padding:0.8em;border-radius:4px;border-left:4px solid #1565c0;\"><p style=\"margin:0 0 0.4em 0;font-size:0.8em;font-weight:bold;color:#1565c0;\">CONCEPT</p><div style=\"font-size:0.85em;\"><p>Unlike the base <code>Shader</code> class, which must parse a combined <code>.glsl</code> file for two stages using the <code>#shader vertex</code> / <code>#shader fragment</code> sentinel pattern, a compute shader is a single unbroken block of GLSL. There is no stage to split — the entire file is the program.</p><p>This means <code>ReadFile</code> can be written as a straightforward whole-file read: open an <code>std::ifstream</code>, stream the buffer into a <code>std::stringstream</code>, return the string. No parsing, no state machine.</p><p>It is worth adding a file-existence check using <code>std::filesystem::exists</code> before attempting to open the stream. A silent open failure produces an empty source string which then causes a cryptic shader compilation error rather than a clear message at the point of failure.</p><p>Note that <code>std::filesystem</code> requires C++17. Check that your project is targeting C++17 or later in the project properties if the include fails.</p></div></div><div><div style=\"background:#fff8e1;padding:0.8em;border-radius:4px;border-left:4px solid #f57f17;\"><p style=\"margin:0 0 0.4em 0;font-size:0.8em;font-weight:bold;color:#e65100;\">YOUR TASK</p><div style=\"font-size:0.85em;\"><ol><li>Open <code>ComputeShader.cpp</code>.</li><li>Implement <code>ComputeShader::ReadFile(const std::string&amp; filepath)</code>.</li><li>Use <code>std::filesystem::exists</code> to print a warning to <code>std::cerr</code> if the file does not exist.</li><li>Open the file with <code>std::ifstream</code>, stream it into a <code>std::stringstream</code>, and return the resulting string.</li><li>Add the required includes: <code>&lt;fstream&gt;</code>, <code>&lt;sstream&gt;</code>, <code>&lt;filesystem&gt;</code>, <code>&lt;iostream&gt;</code>.</li></ol></div></div><div style=\"margin-top:0.6em;\"><p style=\"margin:0 0 0.3em 0;font-size:0.8em;color:#555;font-weight:bold;\">STARTER CODE</p><pre style=\"background:#1e1e1e;color:#d4d4d4;padding:0.8em;border-radius:4px;font-size:0.75em;overflow-x:auto;white-space:pre-wrap;\">#include \"ComputeShader.h\"\n#include &lt;fstream&gt;\n#include &lt;sstream&gt;\n#include &lt;filesystem&gt;\n#include &lt;iostream&gt;\n\nstd::string ComputeShader::ReadFile(const std::string&amp; filepath)\n{\n    // TODO: Check the file exists using std::filesystem::exists.\n    //       If not, print a warning to std::cerr.\n\n    // TODO: Open with std::ifstream, stream into std::stringstream, return the string.\n    return \"\";\n}</pre></div><div style=\"margin-top:0.8em;padding:0.5em 0.8em;background:#e8f5e9;border-left:4px solid #4caf50;border-radius:2px;\"><p style=\"margin:0 0 0.2em 0;font-size:0.8em;font-weight:bold;color:#2e7d32;\">EXPECTED RESULT</p><p style=\"margin:0;font-size:0.85em;\">No visible output yet. If you temporarily pass a non-existent path you should see the warning printed to the console.</p></div></div></div>"
              },
              "subContentId": "aaaaaaaa-0004-4000-8000-000000000000",
              "metadata": {
                "contentType": "Text",
                "license": "U",
                "title": "Untitled Text",
                "authors": [],
                "changes": []
              }
            },
            "alwaysDisplayComments": false,
            "backgroundOpacity": 0,
            "displayAsButton": false,
            "buttonSize": "big",
            "goToSlideType": "specified",
            "invisible": false,
            "solution": ""
          }
        ],
        "slideBackgroundSelector": {},
        "keywords": [
          {
            "main": "Step 2 — ReadFile"
          }
        ]
      },
      {
        "elements": [
          {
            "x": 1,
            "y": 1,
            "width": 98,
            "height": 97,
            "action": {
              "library": "H5P.AdvancedText 1.1",
              "params": {
                "text": "<h2 style=\"margin:0 0 0.5em 0;font-size:1.1em;color:#1a237e;\">Step 3: Compiling the Compute Shader Stage</h2><div style=\"display:grid;grid-template-columns:1fr 1fr;gap:1em;\"><div style=\"background:#f3f4f6;padding:0.8em;border-radius:4px;border-left:4px solid #1565c0;\"><p style=\"margin:0 0 0.4em 0;font-size:0.8em;font-weight:bold;color:#1565c0;\">CONCEPT</p><div style=\"font-size:0.85em;\"><p>OpenGL shader compilation is a three-step process regardless of shader type: <strong>create</strong> a shader object (<code>glCreateShader</code>), <strong>attach</strong> source (<code>glShaderSource</code>), <strong>compile</strong> (<code>glCompileShader</code>). What changes here is the type token passed to <code>glCreateShader</code>: instead of <code>GL_VERTEX_SHADER</code> or <code>GL_FRAGMENT_SHADER</code>, you pass <code>GL_COMPUTE_SHADER</code>.</p><p><code>GL_COMPUTE_SHADER</code> was introduced in <strong>OpenGL 4.3</strong>. If your context is created at 3.3 (the common tutorial default), <code>glCreateShader(GL_COMPUTE_SHADER)</code> will return 0 and subsequent calls will fail silently. You must request a 4.3 or later context when initialising GLFW.</p><p>After calling <code>glCompileShader</code>, always query <code>GL_COMPILE_STATUS</code> with <code>glGetShaderiv</code>. If compilation failed, retrieve the info log with <code>glGetShaderInfoLog</code> and print it before calling <code>glDeleteShader</code> — otherwise the shader object leaks. Return 0 on failure so the caller can detect the problem.</p><p>On MSVC, stack-allocated log buffers can use <code>_malloca</code> rather than a fixed-size array, since the required length is queried first.</p></div></div><div><div style=\"background:#fff8e1;padding:0.8em;border-radius:4px;border-left:4px solid #f57f17;\"><p style=\"margin:0 0 0.4em 0;font-size:0.8em;font-weight:bold;color:#e65100;\">YOUR TASK</p><div style=\"font-size:0.85em;\"><ol><li>Implement <code>ComputeShader::Compile(const std::string&amp; source)</code> in <code>ComputeShader.cpp</code>.</li><li>Create a <code>GL_COMPUTE_SHADER</code> object using <code>glCreateShader</code>.</li><li>Attach the source and compile.</li><li>Query <code>GL_COMPILE_STATUS</code>. On failure, retrieve the info log, print it to <code>std::cerr</code>, delete the shader, and return <code>0</code>.</li><li>Return the shader <code>id</code> on success.</li></ol></div></div><div style=\"margin-top:0.6em;\"><p style=\"margin:0 0 0.3em 0;font-size:0.8em;color:#555;font-weight:bold;\">STARTER CODE</p><pre style=\"background:#1e1e1e;color:#d4d4d4;padding:0.8em;border-radius:4px;font-size:0.75em;overflow-x:auto;white-space:pre-wrap;\">unsigned int ComputeShader::Compile(const std::string&amp; source)\n{\n    // TODO: Create a GL_COMPUTE_SHADER object.\n    unsigned int id = 0; // replace with glCreateShader(...)\n\n    // TODO: Attach source with glShaderSource and compile with glCompileShader.\n\n    // TODO: Query GL_COMPILE_STATUS.\n    //       On failure: get the info log length, allocate with _malloca,\n    //       retrieve with glGetShaderInfoLog, print to std::cerr,\n    //       call glDeleteShader(id), and return 0.\n\n    return id;\n}</pre></div><div style=\"margin-top:0.8em;padding:0.5em 0.8em;background:#e8f5e9;border-left:4px solid #4caf50;border-radius:2px;\"><p style=\"margin:0 0 0.2em 0;font-size:0.8em;font-weight:bold;color:#2e7d32;\">EXPECTED RESULT</p><p style=\"margin:0;font-size:0.85em;\">If you supply a valid <code>.comp</code> file, <code>Compile</code> returns a non-zero shader id. If you introduce a syntax error in the GLSL, the error message appears in the console.</p></div></div></div>"
              },
              "subContentId": "aaaaaaaa-0005-4000-8000-000000000000",
              "metadata": {
                "contentType": "Text",
                "license": "U",
                "title": "Untitled Text",
                "authors": [],
                "changes": []
              }
            },
            "alwaysDisplayComments": false,
            "backgroundOpacity": 0,
            "displayAsButton": false,
            "buttonSize": "big",
            "goToSlideType": "specified",
            "invisible": false,
            "solution": ""
          }
        ],
        "slideBackgroundSelector": {},
        "keywords": [
          {
            "main": "Step 3 — Compile"
          }
        ]
      },
      {
        "elements": [
          {
            "x": 1,
            "y": 1,
            "width": 98,
            "height": 97,
            "action": {
              "library": "H5P.AdvancedText 1.1",
              "params": {
                "text": "<h2 style=\"margin:0 0 0.5em 0;font-size:1.1em;color:#1a237e;\">Step 4: Building the Program Object</h2><div style=\"display:grid;grid-template-columns:1fr 1fr;gap:1em;\"><div style=\"background:#f3f4f6;padding:0.8em;border-radius:4px;border-left:4px solid #1565c0;\"><p style=\"margin:0 0 0.4em 0;font-size:0.8em;font-weight:bold;color:#1565c0;\">CONCEPT</p><div style=\"font-size:0.85em;\"><p>A compiled shader object is not yet usable on its own. OpenGL requires you to attach one or more shader objects to a <strong>program object</strong> and link them before the GPU can execute the code. The program object is what you bind with <code>glUseProgram</code> and what holds the uniform state.</p><p>For a traditional vertex+fragment pipeline you attach two shader objects. For a compute shader you attach <strong>one</strong>. The API calls are otherwise identical: <code>glCreateProgram</code>, <code>glAttachShader</code>, <code>glLinkProgram</code>, <code>glValidateProgram</code>.</p><p>After linking, always query <code>GL_LINK_STATUS</code>. Linking can fail even if each individual shader compiled successfully — for example if the compute shader references a built-in incorrectly or if required extensions are unavailable.</p><p>Once linking succeeds, the shader object is <em>baked in</em>: the program retains a reference to the compiled binary. The separate shader object is no longer needed and should be deleted with <code>glDeleteShader</code> to avoid leaking GPU memory. This is a step the original base-class <code>CreateShader</code> omitted.</p></div></div><div><div style=\"background:#fff8e1;padding:0.8em;border-radius:4px;border-left:4px solid #f57f17;\"><p style=\"margin:0 0 0.4em 0;font-size:0.8em;font-weight:bold;color:#e65100;\">YOUR TASK</p><div style=\"font-size:0.85em;\"><ol><li>Implement the <code>ComputeShader</code> constructor in <code>ComputeShader.cpp</code>.</li><li>In the initialiser list, call the protected base constructor <code>Shader()</code>.</li><li>Store the filepath, call <code>ReadFile</code>, call <code>Compile</code>.</li><li>Call <code>glCreateProgram</code>, <code>glAttachShader</code>, <code>glLinkProgram</code>, <code>glValidateProgram</code>.</li><li>Query <code>GL_LINK_STATUS</code> and print the info log to <code>std::cerr</code> on failure.</li><li>Call <code>glDeleteShader</code> on the shader object after linking.</li></ol></div></div><div style=\"margin-top:0.6em;\"><p style=\"margin:0 0 0.3em 0;font-size:0.8em;color:#555;font-weight:bold;\">STARTER CODE</p><pre style=\"background:#1e1e1e;color:#d4d4d4;padding:0.8em;border-radius:4px;font-size:0.75em;overflow-x:auto;white-space:pre-wrap;\">ComputeShader::ComputeShader(const std::string&amp; filepath)\n    // TODO: call the protected Shader() default constructor here\n{\n    m_Filepath = filepath;\n\n    // TODO: Read the source file.\n    // TODO: Compile the single compute stage.\n\n    // TODO: Create a program object.\n    // TODO: Attach the compiled shader.\n    // TODO: Link and validate the program.\n\n    // TODO: Check GL_LINK_STATUS; print info log to std::cerr on failure.\n\n    // TODO: Delete the shader object — it is no longer needed after linking.\n}</pre></div><div style=\"margin-top:0.8em;padding:0.5em 0.8em;background:#e8f5e9;border-left:4px solid #4caf50;border-radius:2px;\"><p style=\"margin:0 0 0.2em 0;font-size:0.8em;font-weight:bold;color:#2e7d32;\">EXPECTED RESULT</p><p style=\"margin:0;font-size:0.85em;\">Constructing a <code>ComputeShader</code> with a valid <code>.comp</code> file prints nothing to the console and sets <code>m_RendererID</code> to a non-zero program handle.</p></div></div></div>"
              },
              "subContentId": "aaaaaaaa-0006-4000-8000-000000000000",
              "metadata": {
                "contentType": "Text",
                "license": "U",
                "title": "Untitled Text",
                "authors": [],
                "changes": []
              }
            },
            "alwaysDisplayComments": false,
            "backgroundOpacity": 0,
            "displayAsButton": false,
            "buttonSize": "big",
            "goToSlideType": "specified",
            "invisible": false,
            "solution": ""
          }
        ],
        "slideBackgroundSelector": {},
        "keywords": [
          {
            "main": "Step 4 — Constructor"
          }
        ]
      },
      {
        "elements": [
          {
            "x": 1,
            "y": 1,
            "width": 98,
            "height": 97,
            "action": {
              "library": "H5P.AdvancedText 1.1",
              "params": {
                "text": "<h2 style=\"margin:0 0 0.5em 0;font-size:1.1em;color:#1a237e;\">Step 5: Dispatching Work Groups</h2><div style=\"display:grid;grid-template-columns:1fr 1fr;gap:1em;\"><div style=\"background:#f3f4f6;padding:0.8em;border-radius:4px;border-left:4px solid #1565c0;\"><p style=\"margin:0 0 0.4em 0;font-size:0.8em;font-weight:bold;color:#1565c0;\">CONCEPT</p><div style=\"font-size:0.85em;\"><p><code>glDispatchCompute(groupsX, groupsY, groupsZ)</code> is the compute equivalent of a draw call. Rather than processing triangles, it launches a three-dimensional grid of <strong>work groups</strong>. Each work group runs a fixed number of threads defined inside the shader with the <code>layout(local_size_x = N, local_size_y = 1, local_size_z = 1) in;</code> qualifier.</p><p>Total threads launched = <code>groupsX * groupsY * groupsZ * local_size_x * local_size_y * local_size_z</code>.</p><p>For a 1D problem such as updating 100,000 particles with <code>local_size_x = 256</code>, you need <code>ceil(100000 / 256) = 391</code> groups in X, with Y and Z set to 1. This gives 391 * 256 = 100,096 threads — 96 more than needed. Every thread checks its global index against the particle count and returns early if out of range:</p><pre style='background:#1e1e1e;color:#d4d4d4;padding:0.5em;border-radius:3px;font-size:0.8em;'>uint idx = gl_GlobalInvocationID.x;\nif (idx &gt;= u_ParticleCount) return;</pre><p>The dispatch itself is non-blocking: the CPU continues immediately. Results are not guaranteed to be visible to subsequent draw calls until you insert a <code>glMemoryBarrier</code> — covered in Step 6.</p></div></div><div><div style=\"background:#fff8e1;padding:0.8em;border-radius:4px;border-left:4px solid #f57f17;\"><p style=\"margin:0 0 0.4em 0;font-size:0.8em;font-weight:bold;color:#e65100;\">YOUR TASK</p><div style=\"font-size:0.85em;\"><ol><li>Implement <code>ComputeShader::Dispatch</code> in <code>ComputeShader.cpp</code>.</li><li>Call <code>glDispatchCompute(groupsX, groupsY, groupsZ)</code> wrapped in your project's <code>GlCall()</code> macro.</li><li>In your application code (or a short test main), calculate the correct group count for 100,000 particles with a local size of 256: <code>unsigned int groups = (100000 + 255) / 256;</code></li><li>Call <code>shader.Bind()</code>, then <code>shader.Dispatch(groups, 1, 1)</code>.</li></ol></div></div><div style=\"margin-top:0.6em;\"><p style=\"margin:0 0 0.3em 0;font-size:0.8em;color:#555;font-weight:bold;\">STARTER CODE</p><pre style=\"background:#1e1e1e;color:#d4d4d4;padding:0.8em;border-radius:4px;font-size:0.75em;overflow-x:auto;white-space:pre-wrap;\">void ComputeShader::Dispatch(unsigned int groupsX, unsigned int groupsY, unsigned int groupsZ) const\n{\n    // TODO: Call glDispatchCompute, wrapped in GlCall().\n}\n\n// --- In your application / test code: ---\n// unsigned int groups = (particleCount + localSize - 1) / localSize;\n// computeShader.Bind();\n// computeShader.Dispatch(groups, 1, 1);\n// TODO: What is missing here before you use the SSBO in a draw call? (Step 6)</pre></div><div style=\"margin-top:0.8em;padding:0.5em 0.8em;background:#e8f5e9;border-left:4px solid #4caf50;border-radius:2px;\"><p style=\"margin:0 0 0.2em 0;font-size:0.8em;font-weight:bold;color:#2e7d32;\">EXPECTED RESULT</p><p style=\"margin:0;font-size:0.85em;\">No crash or GL error. The dispatch call appears in an OpenGL debug output layer (if enabled) as a compute dispatch event. Particle data in the SSBO is updated on the next frame.</p></div></div></div>"
              },
              "subContentId": "aaaaaaaa-0007-4000-8000-000000000000",
              "metadata": {
                "contentType": "Text",
                "license": "U",
                "title": "Untitled Text",
                "authors": [],
                "changes": []
              }
            },
            "alwaysDisplayComments": false,
            "backgroundOpacity": 0,
            "displayAsButton": false,
            "buttonSize": "big",
            "goToSlideType": "specified",
            "invisible": false,
            "solution": ""
          }
        ],
        "slideBackgroundSelector": {},
        "keywords": [
          {
            "main": "Step 5 — Dispatch"
          }
        ]
      },
      {
        "elements": [
          {
            "x": 1,
            "y": 1,
            "width": 98,
            "height": 97,
            "action": {
              "library": "H5P.AdvancedText 1.1",
              "params": {
                "text": "<h2 style=\"margin:0 0 0.5em 0;font-size:1.1em;color:#1a237e;\">Step 6: Fix the Missing Barrier (Fix and Extend)</h2><div style=\"display:grid;grid-template-columns:1fr 1fr;gap:1em;\"><div style=\"background:#f3f4f6;padding:0.8em;border-radius:4px;border-left:4px solid #1565c0;\"><p style=\"margin:0 0 0.4em 0;font-size:0.8em;font-weight:bold;color:#1565c0;\">CONCEPT</p><div style=\"font-size:0.85em;\"><p>The GPU executes pipeline stages out of order and asynchronously. After <code>glDispatchCompute</code> returns on the CPU, the compute shader may still be running on the GPU. More importantly, even after it finishes, the results written to the SSBO are not automatically visible to the vertex shader in the next draw call — the GPU's caches must be flushed and the writes made coherent.</p><p><code>glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT)</code> inserts a <strong>synchronisation point</strong>: all compute writes to shader storage buffers are completed and made visible before any subsequent shader stage reads from the same buffer.</p><p>Without this barrier, the vertex shader may read stale data from the SSBO — particle positions from the previous frame, or partially updated values from the current one. The resulting artefact is typically flickering or particles appearing frozen, which is plausible and easy to misattribute to a logic bug in the update shader.</p><p>The barrier goes between the dispatch call and the draw call, never inside either. Placing it too early (before the dispatch) has no effect. Placing it too late (after the draw) is also incorrect.</p></div></div><div><div style=\"background:#fff8e1;padding:0.8em;border-radius:4px;border-left:4px solid #f57f17;\"><p style=\"margin:0 0 0.4em 0;font-size:0.8em;font-weight:bold;color:#e65100;\">YOUR TASK</p><div style=\"font-size:0.85em;\"><ol><li>The starter code below contains a deliberate omission: the <code>glMemoryBarrier</code> call is missing between the dispatch and the draw.</li><li>Run the application and observe the symptom — particles will appear to flicker or not update.</li><li>Identify the correct location and add: <code>GlCall(glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT));</code></li><li><strong>Extend:</strong> Add a second compute pass that runs after the barrier and before the draw call. This second pass could, for example, sort or cull particles. Dispatch it with the same group calculation. Add a second barrier after it.</li></ol></div></div><div style=\"margin-top:0.6em;\"><p style=\"margin:0 0 0.3em 0;font-size:0.8em;color:#555;font-weight:bold;\">STARTER CODE</p><pre style=\"background:#1e1e1e;color:#d4d4d4;padding:0.8em;border-radius:4px;font-size:0.75em;overflow-x:auto;white-space:pre-wrap;\">// BUG: glMemoryBarrier is missing — find where it belongs and add it.\n\nvoid ParticleSystem::Update(float dt)\n{\n    m_ComputeShader-&gt;Bind();\n    m_ComputeShader-&gt;setUniform1f(\"u_DeltaTime\", dt);\n    m_ComputeShader-&gt;setUniform1i(\"u_ParticleCount\", m_ParticleCount);\n\n    unsigned int groups = (m_ParticleCount + 255) / 256;\n    m_ComputeShader-&gt;Dispatch(groups, 1, 1);\n\n    // TODO: Add glMemoryBarrier here with the correct barrier bit.\n\n    // TODO (Extension): Bind second compute shader, dispatch, add second barrier.\n\n    m_ComputeShader-&gt;Unbind();\n}\n\nvoid ParticleSystem::Render()\n{\n    // Vertex shader reads SSBO here — must be after barrier.\n    glBindVertexArray(m_VAO);\n    GlCall(glDrawArrays(GL_POINTS, 0, m_ParticleCount));\n    glBindVertexArray(0);\n}</pre></div><div style=\"margin-top:0.8em;padding:0.5em 0.8em;background:#e8f5e9;border-left:4px solid #4caf50;border-radius:2px;\"><p style=\"margin:0 0 0.2em 0;font-size:0.8em;font-weight:bold;color:#2e7d32;\">EXPECTED RESULT</p><p style=\"margin:0;font-size:0.85em;\">After the fix, particles update smoothly with no flickering. After the extension, the second dispatch appears in GPU profiler captures (e.g. RenderDoc) as a separate compute event between the two barrier markers.</p></div></div></div>"
              },
              "subContentId": "aaaaaaaa-0008-4000-8000-000000000000",
              "metadata": {
                "contentType": "Text",
                "license": "U",
                "title": "Untitled Text",
                "authors": [],
                "changes": []
              }
            },
            "alwaysDisplayComments": false,
            "backgroundOpacity": 0,
            "displayAsButton": false,
            "buttonSize": "big",
            "goToSlideType": "specified",
            "invisible": false,
            "solution": ""
          }
        ],
        "slideBackgroundSelector": {},
        "keywords": [
          {
            "main": "Step 6 — Memory Barrier"
          }
        ]
      }
    ],
    "keywordListEnabled": true,
    "globalBackgroundSelector": {},
    "keywordListAlwaysShow": false,
    "keywordListAutoHide": false,
    "keywordListOpacity": 90
  },
  "override": {
    "activeSurface": false,
    "hideSummarySlide": true,
    "summarySlideSolutionButton": true,
    "summarySlideRetryButton": true,
    "enablePrintButton": false,
    "social": {
      "showFacebookShare": false,
      "facebookShare": {
        "url": "@currentpageurl",
        "quote": ""
      },
      "showGoogleShare": false,
      "googleShareUrl": "@currentpageurl",
      "showTwitterShare": false,
      "twitterShare": {
        "statement": "",
        "url": "@currentpageurl",
        "hashtags": ""
      }
    }
  },
  "l10n": {
    "slide": "Slide",
    "score": "Score",
    "yourScore": "Your score",
    "maxScore": "Max score",
    "next": "Next",
    "prev": "Previous",
    "navigateToSlide": "Navigate to slide :num",
    "skip": "Skip referer",
    "done": "Done",
    "title": "Title",
    "fullscreen": "Fullscreen",
    "exitFullscreen": "Exit fullscreen",
    "shareResult": "Share Result",
    "accessibilitySlideNavigationExplanation": "Use left and right arrow to change slide.",
    "accessibilityCanvasLabel": "Presentation canvas. Use left and right arrow to move between slides.",
    "containsNotCompleted": "@slideName contains not completed interaction",
    "containsCompleted": "@slideName contains completed interaction",
    "slideCount": "Slide @index of @total",
    "currentSlide": "Current slide",
    "navigationSlide": "Navigation slide",
    "swipeLeftToNext": "Swipe left to go to next slide",
    "swipeRightToPrev": "Swipe right to go to previous slide"
  }
}