diff --git a/engine/src/flutter/impeller/README.md b/engine/src/flutter/impeller/README.md index b5ba53153eb..0af619140a1 100644 --- a/engine/src/flutter/impeller/README.md +++ b/engine/src/flutter/impeller/README.md @@ -154,9 +154,11 @@ states of completion: ## Try Impeller in Flutter -Impeller is available under the `--enable-impeller` flag on iOS and Android. This flag can be specified to `flutter run`. +Impeller is available under the `--enable-impeller` flag on iOS and Android. +This flag can be specified to `flutter run`. -If the application needs to be launched with Impeller enabled without using the Flutter tool, follow the platform specific steps below. +If the application needs to be launched with Impeller enabled without using the +Flutter tool, follow the platform specific steps below. ### iOS @@ -177,7 +179,7 @@ To your `AndroidManifest.xml` file, add under the `` tag: ## Documentation, References, and Additional Reading -* [Learning to Read GPU Frame Captures.](docs/read_frame_captures.md) +* [Impellers Coordinate System](docs/coordinate_system.md) * [How to Setup Xcode for GPU Frame Captures with Metal.](docs/xcode_frame_capture.md) * [How to Enable Metal Validation for Command Line Apps.](docs/metal_validation.md) * [How Impeller Works Around The Lack of Uniform Buffers in Open GL ES 2.0.](docs/ubo_gles2.md) diff --git a/engine/src/flutter/impeller/docs/coordinate_system.md b/engine/src/flutter/impeller/docs/coordinate_system.md new file mode 100644 index 00000000000..9010610f3c9 --- /dev/null +++ b/engine/src/flutter/impeller/docs/coordinate_system.md @@ -0,0 +1,31 @@ +# Impellers Coordinate System + +**TL;DR, Impeller uses the Metal coordinate system.** + +This document describes the Impeller coordinate system. This is the coordinate +system assumed by all Impeller sub-subsystems and users of Impeller itself. + +All sub-systems that deal with interacting with backend client rendering APIs +(like OpenGL, Metal, Direct3D, Dawn, etc..) must reconcile with Impellers +coordinate system. + +While the readers familiarity with a particular coordinate system might make +them think otherwise, there is no right or wrong coordinate system. However, +having a consist notion of a coordinate system is essential. Since the Metal +backend was the first Impeller backend, the Metal coordinate system was picked +as the Impeller coordinate system with very little consideration of +alternatives. + +The following table describes the Impeller coordinate system along with how it +differs with that of popular client rendering APIs and backends. + +| API | Normalized Device Coordinate | Viewport / Framebuffer Coordinate | Texture Coordinate | +|---------------|-------------------------------------------------------|---------------------------------------|--------------------------------------| +| **Impeller** | `(-1,-1)` Bottom-Left, `(+1,+1)` Top-Right, `+Y` up. | `(0,0)` Top-Left Origin, `+Y` down. | `(0,0)` Top-Left Origin, `+Y` down. | +| **Metal** | `(-1,-1)` Bottom-Left, `(+1,+1)` Top-Right, `+Y` up. | `(0,0)` Top-Left Origin, `+Y` down. | `(0,0)` Top-Left Origin, `+Y` down. | +| **OpenGL** | `(-1,-1)` Bottom-Left, `(+1,+1)` Top-Right, `+Y` up. | `(0,0)` Bottom-Left Origin, `+Y` up. | `(0,0)` Bottom-Left Origin, `+Y` up. | +| **OpenGL ES** | `(-1,-1)` Bottom-Left, `(+1,+1)` Top-Right, `+Y` up. | `(0,0)` Bottom-Left Origin, `+Y` up. | `(0,0)` Bottom-Left Origin, `+Y` up. | +| **WebGL** | `(-1,-1)` Bottom-Left, `(+1,+1)` Top-Right, `+Y` up. | `(0,0)` Bottom-Left Origin, `+Y` up. | `(0,0)` Bottom-Left Origin, `+Y` up. | +| **Direct 3D** | `(-1,-1)` Bottom-Left, `(+1,+1)` Top-Right, `+Y` up. | `(0,0)` Top-Left Origin, `+Y` down. | `(0,0)` Top-Left Origin, `+Y` down. | +| **Vulkan** | `(-1,-1)` Top-Left, `(+1,+1)` Bottom-Right, `+Y` down.| `(0,0)` Top-Left Origin, `+Y` down. | `(0,0)` Top-Left Origin, `+Y` down. | +| **WebGPU** | `(-1,-1)` Bottom-Left, `(+1,+1)` Top-Right, `+Y` up. | `(0,0)` Top-Left Origin, `+Y` down. | `(0,0)` Top-Left Origin, `+Y` down. | diff --git a/engine/src/flutter/impeller/docs/metal_validation.md b/engine/src/flutter/impeller/docs/metal_validation.md index 92f40a5bb16..a471155ee96 100644 --- a/engine/src/flutter/impeller/docs/metal_validation.md +++ b/engine/src/flutter/impeller/docs/metal_validation.md @@ -1,8 +1,10 @@ # Enable Metal Validation without Xcode. -To enable validation of all Metal calls without using Xcode, add the following to your rc file. +To enable validation of all Metal calls without using Xcode, add the following +to your rc file. -These flags are not documented and have been reverse engineered from observing what Xcode does to enable these validation layers. +These flags are not documented and have been reverse engineered from observing +what Xcode does to enable these validation layers. ``` diff --git a/engine/src/flutter/impeller/docs/read_frame_captures.md b/engine/src/flutter/impeller/docs/read_frame_captures.md index aef3a1bf1dc..a7b1dead10e 100644 --- a/engine/src/flutter/impeller/docs/read_frame_captures.md +++ b/engine/src/flutter/impeller/docs/read_frame_captures.md @@ -1,272 +1,453 @@ # Learning to Read GPU Frame Captures -This is a gentle introduction to learning how one may go about reading a GPU frame capture. This would be using a tool like the [Xcode GPU Frame Debugger](https://developer.apple.com/documentation/metal/frame_capture_debugging_tools?language=objc), [RenderDoc](https://renderdoc.org/), or [Android GPU Inspector](https://gpuinspector.dev/). If you are already comfortable using one or all of these tools, this introduction is likely too rudimentary for you. If so, please skip this. +This is a gentle introduction to learning how one may go about reading a GPU +frame capture. This would be using a tool like the [Xcode GPU Frame +Debugger](https://developer.apple.com/documentation/metal/frame_capture_debugging_tools?language=objc), +[RenderDoc](https://renderdoc.org/), or [Android GPU +Inspector](https://gpuinspector.dev/). If you are already comfortable using one +or all of these tools, this introduction is likely too rudimentary for you. If +so, please skip this. -If you are working on Impeller (or any low-level graphics API for that matter), it is unlikely you are going to get any work done without a frame debugger. Fortunately, it is also extremely easy and fun. But it does require learning a new skill-set. +If you are working on Impeller (or any low-level graphics API for that matter), +it is unlikely you are going to get any work done without a frame debugger. +Fortunately, it is also extremely easy and fun. But it does require learning a +new skill-set. -I liken getting proficient at graphics debugging to learning how to drive. It is absolutely a skill-set that must be learned. And, you get better at it the more you practice. - -The car you choose to learn to drive on really doesn’t matter. It may be gas or electric, stick-shift or automatic. Admittedly, some cars are easier to learn to drive on than others. But again, the car isn’t the point. The same holds for graphics debuggers and the client rendering APIs. If you can read a GPU Frame capture of a Vulkan frame on Windows using RenderDoc, you should be quickly able to read a trace for a Metal Frame on iOS using Xcode. In fact, in a cross-platform framework like Impeller, it is unlikely you are going to be able to get away with using just one debugger. Like cars, all of them have their own quirks and use-cases with no one-size-fits all solution. +I liken getting proficient at graphics debugging to learning how to drive. It is +absolutely a skill-set that must be learned. And, you get better at it the more +you practice. +The car you choose to learn to drive on really doesn’t matter. It may be gas or +electric, stick-shift or automatic. Admittedly, some cars are easier to learn to +drive on than others. But again, the car isn’t the point. The same holds for +graphics debuggers and the client rendering APIs. If you can read a GPU Frame +capture of a Vulkan frame on Windows using RenderDoc, you should be quickly able +to read a trace for a Metal Frame on iOS using Xcode. In fact, in a +cross-platform framework like Impeller, it is unlikely you are going to be able +to get away with using just one debugger. Like cars, all of them have their own +quirks and use-cases with no one-size-fits all solution. # Start in an Empty Parking Lot -You wouldn’t start learning to drive on a busy freeway or city street. So, if you immediately open a frame trace of a very complicated application, you are likely to get overwhelmed. - -Start with a frame that renders absolutely nothing. You are only figuring out what the pedals in the car are and what the gauges mean. We are going to be using Xcode in this tutorial in case you are following along. But again, the car doesn’t matter. - -Make sure you have already set up an Xcode session by following the instructions in the wiki. Setup a test that opens a blank playground. With the playground running, click on the stylized `M` to capture a Metal frame. +You wouldn’t start learning to drive on a busy freeway or city street. So, if +you immediately open a frame trace of a very complicated application, you are +likely to get overwhelmed. +Start with a frame that renders absolutely nothing. You are only figuring out +what the pedals in the car are and what the gauges mean. We are going to be +using Xcode in this tutorial in case you are following along. But again, the car +doesn’t matter. +Make sure you have already set up an Xcode session by following the instructions +in the wiki. Setup a test that opens a blank playground. With the playground +running, click on the stylized `M` to capture a Metal frame. ![alt_text](assets/read_frame_captures/image1.png "image_tooltip") - Give Xcode a few seconds to capture the frame and show the frame overview. - ![alt_text](assets/read_frame_captures/image2.png "image_tooltip") - Let’s figure out what the gauges of this car mean. +* In box `4`, the overview shows that there are no draw calls and one command + buffer with one render command encoder. This is for the playground to render + the blank screen with the clear-color. + * The playground renders a dark slate gray clear color as it was adequately + contrasty with the primary colors and also black and white. +* Box `2` shows the Metal calls made grouped by the API call. If you click on + the Group by API Call dropdown, you can group the calls made according to the + pipeline state. But we have no draw calls remember, so this will be empty. + But, realize that in a more complicated application where you are looking for + a single class of draw calls, viewing by the pipeline state is going to be + more useful. +* When grouping by the API call, absolutely all calls made to the Metal API will + be shown in the sidebar. Most of them are not particularly interesting. These + include calls to allocate memory, create command buffers, set labels, etc.. To + whittle down this list to a (potentially) more interesting subset, click the + little flag at the bottom (see box `8`). But, if you ever find a call you were + looking for is not present in the sidebar, you may have filtered it away. +* Box `5` shows frame performance. But there is nothing to show as we are not + rendering anything. We’ll come back to this later. +* Box `6` shows the graphics memory overview. We’ll revisit this in detail later + too. But, it is a good idea to see what memory rendering a blank slate needs. + Realize that all graphics memory is not equal and learning when to use one vs + the other can lead to some interesting performance improvements. +* Box `7` is Xcodes attempt at showing you how you can improve performance. + These are just insights though and not warnings or errors. But, in every + frame, try to understand and reason about each insight to see if action is + necessary. In most cases, you can address these insights fairly easily. In the + example above, there are three insights. Lets reason about them: + * There are two insights for the late creation of two textures. From the + names of the textures, you can tell that one is the texture used for the + stencil buffer and another the color texture used for the 4xMSAA resolve + step. Impeller uses memory-less textures for those on iOS and the + playground is running on Mac. So it hasn’t bothered to create and reuse + textures in the playground runner. But, it should. And Xcode’s point that + texture allocations should not occur in a frame workload is well made. + Advice that is universally prudent when working on Impeller. + * The last insight is that the main render pass is empty. Well, no shit, + Sherlock. We won’t have this concern in a real application. The playground + will always render frames over and over specifically so that a frame + debugger can capture a frame. Even if nothing is in that frame. This won’t + be a problem in Flutter where no frame will be rendered if nothing + changes. + * Notice that we could immediately tell what the two textures that were + created late were for. This is because all GPU objects in Impeller have + the ability to be labelled. In fact most APIs in Impeller make it very + hard to create unlabelled objects. If you notice an object that is not + labelled, file a bug to label it. Better yet, find and label it yourself. + Building for easier instrumentation must be done diligently and + proactively. And it is your responsibility! +* Box `1` is the nav stack that you will use often and is unreasonably effective + in Xcode relative to other debuggers. It’s a good idea to remember its key + binding (mine is ctrl+cmd+arrow). If you click on something and find yourself + lost, go back to a known point (usually the summary). +* Box `3` highlights an `Export` button. This allows you to export a GPU trace. + But, realize that whoever views a GPU Trace needs to have identical hardware. + The traces are also rather large. So, in a single debugging session, you + should store these traces locally so you can check how your iterations are + affecting the frame. But you may not find sending these to others super + useful. - -* In box `4`, the overview shows that there are no draw calls and one command buffer with one render command encoder. This is for the playground to render the blank screen with the clear-color. - * The playground renders a dark slate gray clear color as it was adequately contrasty with the primary colors and also black and white. -* Box `2` shows the Metal calls made grouped by the API call. If you click on the Group by API Call dropdown, you can group the calls made according to the pipeline state. But we have no draw calls remember, so this will be empty. But, realize that in a more complicated application where you are looking for a single class of draw calls, viewing by the pipeline state is going to be more useful. -* When grouping by the API call, absolutely all calls made to the Metal API will be shown in the sidebar. Most of them are not particularly interesting. These include calls to allocate memory, create command buffers, set labels, etc.. To whittle down this list to a (potentially) more interesting subset, click the little flag at the bottom (see box `8`). But, if you ever find a call you were looking for is not present in the sidebar, you may have filtered it away. -* Box `5` shows frame performance. But there is nothing to show as we are not rendering anything. We’ll come back to this later. -* Box `6` shows the graphics memory overview. We’ll revisit this in detail later too. But, it is a good idea to see what memory rendering a blank slate needs. Realize that all graphics memory is not equal and learning when to use one vs the other can lead to some interesting performance improvements. -* Box `7` is Xcodes attempt at showing you how you can improve performance. These are just insights though and not warnings or errors. But, in every frame, try to understand and reason about each insight to see if action is necessary. In most cases, you can address these insights fairly easily. In the example above, there are three insights. Lets reason about them: - * There are two insights for the late creation of two textures. From the names of the textures, you can tell that one is the texture used for the stencil buffer and another the color texture used for the 4xMSAA resolve step. Impeller uses memory-less textures for those on iOS and the playground is running on Mac. So it hasn’t bothered to create and reuse textures in the playground runner. But, it should. And Xcode’s point that texture allocations should not occur in a frame workload is well made. Advice that is universally prudent when working on Impeller. - * The last insight is that the main render pass is empty. Well, no shit, Sherlock. We won’t have this concern in a real application. The playground will always render frames over and over specifically so that a frame debugger can capture a frame. Even if nothing is in that frame. This won’t be a problem in Flutter where no frame will be rendered if nothing changes. - * Notice that we could immediately tell what the two textures that were created late were for. This is because all GPU objects in Impeller have the ability to be labelled. In fact most APIs in Impeller make it very hard to create unlabelled objects. If you notice an object that is not labelled, file a bug to label it. Better yet, find and label it yourself. Building for easier instrumentation must be done diligently and proactively. And it is your responsibility! -* Box `1` is the nav stack that you will use often and is unreasonably effective in Xcode relative to other debuggers. It’s a good idea to remember its key binding (mine is ctrl+cmd+arrow). If you click on something and find yourself lost, go back to a known point (usually the summary). -* Box `3` highlights an `Export` button. This allows you to export a GPU trace. But, realize that whoever views a GPU Trace needs to have identical hardware. The traces are also rather large. So, in a single debugging session, you should store these traces locally so you can check how your iterations are affecting the frame. But you may not find sending these to others super useful. - -Before we trace anything more complicated, let’s take a look at the memory usage in the playground. - +Before we trace anything more complicated, let’s take a look at the memory usage +in the playground. # Memory Overview -Click on the `Show Memory` button in Box `6` from the previous section. We are still not rendering anything in the playground. +Click on the `Show Memory` button in Box `6` from the previous section. We are +still not rendering anything in the playground. An overview of all graphics memory usage is shown. - ![alt_text](assets/read_frame_captures/image3.png "image_tooltip") +Along with all the objects that occupy memory, their locations in different +memory categorizations are also shown. Notice how the totals all add up to the +same number. This is useful in cases where you forgot to assign the optimum +memory storage mode for textures or buffers (private, managed, or memory-less). -Along with all the objects that occupy memory, their locations in different memory categorizations are also shown. Notice how the totals all add up to the same number. This is useful in cases where you forgot to assign the optimum memory storage mode for textures or buffers (private, managed, or memory-less). - -You can double click an object to inspect it and highlighting a texture should give you a preview of its contents. - +You can double click an object to inspect it and highlighting a texture should +give you a preview of its contents. ![alt_text](assets/read_frame_captures/image4.png "image_tooltip") - -Do not underestimate the usefulness of filtering the results either by category name or resource name. You can filter by category by selecting the small circular callstack button next to the category. When you apply filters, the memory totals will update to reflect just filtered items. Here, there is 3 MB of device memory for managed textures. - +Do not underestimate the usefulness of filtering the results either by category +name or resource name. You can filter by category by selecting the small +circular callstack button next to the category. When you apply filters, the +memory totals will update to reflect just filtered items. Here, there is 3 MB of +device memory for managed textures. ![alt_text](assets/read_frame_captures/image5.png "image_tooltip") +You can also apply freeform text filters to the resources using the text field +highlighted by the second box. This is used by multiple Impeller subsystems. For +example, offscreen textures that are used within a frame between multiple +render-passes are named such that they can be easily filtered. So, if you wanted +to estimate the memory overhead of such render-passes (say you are working on +optimizations to elide these), you can easily tell using a simple text filter. -You can also apply freeform text filters to the resources using the text field highlighted by the second box. This is used by multiple Impeller subsystems. For example, offscreen textures that are used within a frame between multiple render-passes are named such that they can be easily filtered. So, if you wanted to estimate the memory overhead of such render-passes (say you are working on optimizations to elide these), you can easily tell using a simple text filter. - -This also highlights the importance of always naming all GPU resources. Again, if you find an unnamed resource in this view, file a but to have it be tagged or [tag it yourself](#finding-where-api-calls-were-made-in-the-codebase). - -The “Time Since Last Used” is useful in catching potential memory leaks as allocations not referenced for multiple frames must typically be collected to save memory. Flutter applications typically have tons of these as its image caches reference images that aren’t used for a while. If these are tagged correctly (which they should be), they can be filtered away. That way, you can focus on leaks in specific subsystems without having “cached” items confuse the memory view. +This also highlights the importance of always naming all GPU resources. Again, +if you find an unnamed resource in this view, file a but to have it be tagged or +[tag it yourself](#finding-where-api-calls-were-made-in-the-codebase). +The “Time Since Last Used” is useful in catching potential memory leaks as +allocations not referenced for multiple frames must typically be collected to +save memory. Flutter applications typically have tons of these as its image +caches reference images that aren’t used for a while. If these are tagged +correctly (which they should be), they can be filtered away. That way, you can +focus on leaks in specific subsystems without having “cached” items confuse the +memory view. # Driving on the Street -So we’re comfortable with the car in the parking lot and we know what all the gauges and pedals do. Let’s drive this car onto a quiet street. - -Let’s render a scene that actually renders something. But, let’s just render a solid red triangle in the playground. +So we’re comfortable with the car in the parking lot and we know what all the +gauges and pedals do. Let’s drive this car onto a quiet street. +Let’s render a scene that actually renders something. But, let’s just render a +solid red triangle in the playground. ![alt_text](assets/read_frame_captures/image6.png "image_tooltip") - We notice two changes in the overview. - - -* When grouping the calls by the pipeline state, we see one pipeline listed with one draw call. Since all GPU objects in Impeller are labelled, we see one pipeline aptly called the `SolidFillPipeline` with one draw call. +* When grouping the calls by the pipeline state, we see one pipeline listed with + one draw call. Since all GPU objects in Impeller are labelled, we see one + pipeline aptly called the `SolidFillPipeline` with one draw call. * The `Performance` section in box `5` from the last section is no longer empty. Let’s dive into each of the new sections. - ## Inspecting the Pipeline State Object -All draw calls use a pipeline state object that specifies the programmable and fixed function elements of the draw call as well as the data referenced by that draw call. +All draw calls use a pipeline state object that specifies the programmable and +fixed function elements of the draw call as well as the data referenced by that +draw call. -The programmable elements of the pipeline state object are defined by shaders that are written on the host and compiled into the engine in an intermediate representation. Vertex shaders are run per vertex in the draw call and fragment shaders run once per texture element in the coverage area of the draw call. +The programmable elements of the pipeline state object are defined by shaders +that are written on the host and compiled into the engine in an intermediate +representation. Vertex shaders are run per vertex in the draw call and fragment +shaders run once per texture element in the coverage area of the draw call. -There are numerous fixed function elements in a pipeline state object. But the major ones Impeller typically must configure are blend modes (i.e, how the new texture element must combine with items already in the framebuffer), sample counts for resolve (used in MSAA), pixel formats of its various attachments, etc.. +There are numerous fixed function elements in a pipeline state object. But the +major ones Impeller typically must configure are blend modes (i.e, how the new +texture element must combine with items already in the framebuffer), sample +counts for resolve (used in MSAA), pixel formats of its various attachments, +etc.. -Pipeline state objects are immutable. So, if either the programmable or fixed function element of the object needs to be modified, a new variant must be created. +Pipeline state objects are immutable. So, if either the programmable or fixed +function element of the object needs to be modified, a new variant must be +created. -So, if you see multiple instances of a named pipeline in the grouping of calls by pipeline state, realize that it is a different variant of a prototype pipeline state. If these are not named appropriately and you can’t tell the difference, file a bug to disambiguate them or [tag them yourself](#finding-where-api-calls-were-made-in-the-codebase)! - -Let’s click on the `SolidFill Pipeline` in the example to analyze that pipeline. All draw calls listed below that pipeline use the same programmable and fixed function pipeline configuration. +So, if you see multiple instances of a named pipeline in the grouping of calls +by pipeline state, realize that it is a different variant of a prototype +pipeline state. If these are not named appropriately and you can’t tell the +difference, file a bug to disambiguate them or [tag them +yourself](#finding-where-api-calls-were-made-in-the-codebase)! +Let’s click on the `SolidFill Pipeline` in the example to analyze that pipeline. +All draw calls listed below that pipeline use the same programmable and fixed +function pipeline configuration. ![alt_text](assets/read_frame_captures/image7.png "image_tooltip") +You will get intimately familiar with this view when you setup a new pipeline +state object in Impeller or try to reason about the correctness of one of the +pipeline state object variants. -You will get intimately familiar with this view when you setup a new pipeline state object in Impeller or try to reason about the correctness of one of the pipeline state object variants. +In this example, we can tell that all draw calls with this pipeline state have +blending enabled with the given blend mode and work on images with `BGRA8Unorm` +pixel format. The draw call can also expect a stencil buffer. -In this example, we can tell that all draw calls with this pipeline state have blending enabled with the given blend mode and work on images with `BGRA8Unorm` pixel format. The draw call can also expect a stencil buffer. - -Clicking on either the vertex or fragment shader should show the equivalent Metal source code for the GLSL shader written in Impeller. This Metal source code (and the shader debugger) is only available in debug and profile modes. When GLSL shaders are written in Impeller, they are converted into intermediate representation for packaging with the engine. However, since debugging shaders is such a useful exercise, the shader compiler will also compile the GLSL shaders into Metal source code and then package it with the debug or profile engine alongside the intermediate representation that is actually used. That way, the Xcode frame debugger can find that code when you ask to debug the programmable elements of the pipeline. - -We'll go into using the shader debugger later. But, now you know how to inspect a pipeline. +Clicking on either the vertex or fragment shader should show the equivalent +Metal source code for the GLSL shader written in Impeller. This Metal source +code (and the shader debugger) is only available in debug and profile modes. +When GLSL shaders are written in Impeller, they are converted into intermediate +representation for packaging with the engine. However, since debugging shaders +is such a useful exercise, the shader compiler will also compile the GLSL +shaders into Metal source code and then package it with the debug or profile +engine alongside the intermediate representation that is actually used. That +way, the Xcode frame debugger can find that code when you ask to debug the +programmable elements of the pipeline. +We'll go into using the shader debugger later. But, now you know how to inspect +a pipeline. ## Inspecting a Single Draw Call -Each draw call must reference a pipeline state (that we already know how to inspect) and provide references to the data used by that draw call (like vertex and uniform buffers, attachments) along with metadata about it (like primitive topology). +Each draw call must reference a pipeline state (that we already know how to +inspect) and provide references to the data used by that draw call (like vertex +and uniform buffers, attachments) along with metadata about it (like primitive +topology). To inspect how each draw call is configured, select the call in the sidebar. - ![alt_text](assets/read_frame_captures/image8.png "image_tooltip") +To get an overview of the draw call, the Bound Resources section is the most +useful view. Let’s ensure we understand each item. -To get an overview of the draw call, the Bound Resources section is the most useful view. Let’s ensure we understand each item. - -The `Pipeline States` section we have already [covered in detail](#inspecting-the-pipeline-state-object). - -In the `Vertex` section, the `Geometry` lists how each vertex is transformed by the vertex shader. +The `Pipeline States` section we have already [covered in +detail](#inspecting-the-pipeline-state-object). +In the `Vertex` section, the `Geometry` lists how each vertex is transformed by +the vertex shader. ![alt_text](assets/read_frame_captures/image9.png "image_tooltip") +Here, you see how each vertex (three here since we are rendering a triangle) is +transformed by the shader such that it ends up in the correct spot in normalized +device coordinates. In this particular case, the solid color seems to be +presented to the vertex shader in a uniform with the shader passing it along to +the fragment stage as an output. An improvement could be to present the uniform +directly to the fragment stage. Impeller may have done this because only a +single uniform buffer for all stages was easier to set up. -Here, you see how each vertex (three here since we are rendering a triangle) is transformed by the shader such that it ends up in the correct spot in normalized device coordinates. In this particular case, the solid color seems to be presented to the vertex shader in a uniform with the shader passing it along to the fragment stage as an output. An improvement could be to present the uniform directly to the fragment stage. Impeller may have done this because only a single uniform buffer for all stages was easier to set up. - -You can double click on any buffer in the `Bound Resources` section to dump information about that buffer presented in a view appropriate for that stage. When I double click the buffer containing the uniform data, the following view is shown. - +You can double click on any buffer in the `Bound Resources` section to dump +information about that buffer presented in a view appropriate for that stage. +When I double click the buffer containing the uniform data, the following view +is shown. ![alt_text](assets/read_frame_captures/image10.png "image_tooltip") +Pay specific attention to the `Row` index. Impeller architecture doesn’t create +small individual buffers for uniform data. All uniform data for a single render +pass is packed into a single jumbo uniform buffer with each draw call +referencing its uniform data at an offset into this larger buffer. This allows +Impeller to avoid small allocations and use a simpler and faster bump allocator. +Here, it looks like the uniform data is towards the end of that jumbo buffer as +noted by the negative indices present in the view. The data at negative indices +is interpreted as garbage when viewed through the lens of the uniform data +layout the draw call expects. -Pay specific attention to the `Row` index. Impeller architecture doesn’t create small individual buffers for uniform data. All uniform data for a single render pass is packed into a single jumbo uniform buffer with each draw call referencing its uniform data at an offset into this larger buffer. This allows Impeller to avoid small allocations and use a simpler and faster bump allocator. Here, it looks like the uniform data is towards the end of that jumbo buffer as noted by the negative indices present in the view. The data at negative indices is interpreted as garbage when viewed through the lens of the uniform data layout the draw call expects. +The other useful item in the `Bound Resources` section is the state of the +attachments when the draw call was made. This comes in particularly handy for +debugging writes to a buffer that you will never actually see. For instance, the +stencil buffers. -The other useful item in the `Bound Resources` section is the state of the attachments when the draw call was made. This comes in particularly handy for debugging writes to a buffer that you will never actually see. For instance, the stencil buffers. - -To demonstrate debugging stencil buffers, I captured a trace of a Fuchsia colored rectangle clipped to a circular shape. You’d never see the stencil buffer so it would be hard to understand how the draw call is affecting it without viewing the attachment in the frame debugger. Clicking on the gear to the right of the buffer label also shows a histogram of the image as well as options to change the color mapping, or, to view values within a certain range. In this simple example, the values in the stencil buffer only range from 0 to 2. So viewing the entire range of values in the stencil buffer would have made the changes in the buffer indiscernible to you. Xcode helpfully selected the “Min to Max” view for us. You can do the same for any attachment. +To demonstrate debugging stencil buffers, I captured a trace of a Fuchsia +colored rectangle clipped to a circular shape. You’d never see the stencil +buffer so it would be hard to understand how the draw call is affecting it +without viewing the attachment in the frame debugger. Clicking on the gear to +the right of the buffer label also shows a histogram of the image as well as +options to change the color mapping, or, to view values within a certain range. +In this simple example, the values in the stencil buffer only range from 0 to 2. +So viewing the entire range of values in the stencil buffer would have made the +changes in the buffer indiscernible to you. Xcode helpfully selected the “Min to +Max” view for us. You can do the same for any attachment. ![alt_text](assets/read_frame_captures/image11.png "image_tooltip") - - ## Debugging a Shader -The shaders authored in Impeller use [GLSL 4.60](https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf). Xcode does not support debugging these shaders natively. To work around this, the Impeller shader compiler will convert those shaders to Metal source code and embed them inside debug and profile mode engine binaries alongside the shaders intermediate representation that is actually used to generate the pipeline state objects. The Metal source code is converted such that it looks as similar to GLSL as possible. +The shaders authored in Impeller use [GLSL +4.60](https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf). +Xcode does not support debugging these shaders natively. To work around this, +the Impeller shader compiler will convert those shaders to Metal source code and +embed them inside debug and profile mode engine binaries alongside the shaders +intermediate representation that is actually used to generate the pipeline state +objects. The Metal source code is converted such that it looks as similar to +GLSL as possible. -You can debug both vertex and fragment shaders. Remember that vertex shaders run once per vertex (three times in the case of our example that renders a simple triangle) and fragment shaders run once per texture element in the coverage area of the draw call (potentially thousands of times depending on the side of the triangle rendered). So, when you want to debug a shader, you must first find one specific invocation of either the vertex or fragment shader to debug. +You can debug both vertex and fragment shaders. Remember that vertex shaders run +once per vertex (three times in the case of our example that renders a simple +triangle) and fragment shaders run once per texture element in the coverage area +of the draw call (potentially thousands of times depending on the side of the +triangle rendered). So, when you want to debug a shader, you must first find one +specific invocation of either the vertex or fragment shader to debug. ### Debugging a Fragment Shader -Since fragment shaders are run once per texture element in the coverage area of the draw call, it is easiest to find invocations of the fragment shader by opening one of the attachments used by the draw call. +Since fragment shaders are run once per texture element in the coverage area of +the draw call, it is easiest to find invocations of the fragment shader by +opening one of the attachments used by the draw call. -Find and open either the color or stencil attachments in the `Bound Resources` section as described in the section on [inspecting a single draw call](#inspecting-a-single-draw-call). +Find and open either the color or stencil attachments in the `Bound Resources` +section as described in the section on [inspecting a single draw +call](#inspecting-a-single-draw-call). ![alt_text](assets/read_frame_captures/image12.png "image_tooltip") -At the bottom right corner of the attachment preview, you will see a disabled `Debug` button with a crosshair to its right. The button is disabled because no texture element is selected for debugging. Click on the crosshair and drag the magnifier on a texture element converted by a draw call. The draw call will be highlighted with a green outline. - -Once a valid texture element is highlighted, the `Debug` button should be enabled. Click it to debug that one invocation of the fragment shader used by that draw call. - +At the bottom right corner of the attachment preview, you will see a disabled +`Debug` button with a crosshair to its right. The button is disabled because no +texture element is selected for debugging. Click on the crosshair and drag the +magnifier on a texture element converted by a draw call. The draw call will be +highlighted with a green outline. +Once a valid texture element is highlighted, the `Debug` button should be +enabled. Click it to debug that one invocation of the fragment shader used by +that draw call. ![alt_text](assets/read_frame_captures/image13.png "image_tooltip") -In the sidebar on the left, each step of execution of the fragment shader is listed. You can click on each to move back and forth through the invocation. The values of local variables will be updated as you do. +In the sidebar on the left, each step of execution of the fragment shader is +listed. You can click on each to move back and forth through the invocation. The +values of local variables will be updated as you do. Some of the usual things to look out for when debugging fragment shaders: - - -* Pay attention to the input to the fragment stage from the vertex stage. This is present in the argument marked with `[[stage_in]]`. -* The output of the stage (which defines the color of the texture element for that invocation) is the return value of the invocation. -* If you aren’t sure of a particular operation within the shader, try adding intermediate variables to the shader. The Impeller shader compiler will faithfully add those intermediate for ease of debugging. Optimizations that hinder debuggability are reserved for optimized release modes and occur on the intermediate representation. +* Pay attention to the input to the fragment stage from the vertex stage. This + is present in the argument marked with `[[stage_in]]`. +* The output of the stage (which defines the color of the texture element for + that invocation) is the return value of the invocation. +* If you aren’t sure of a particular operation within the shader, try adding + intermediate variables to the shader. The Impeller shader compiler will + faithfully add those intermediate for ease of debugging. Optimizations that + hinder debuggability are reserved for optimized release modes and occur on the + intermediate representation. ### Debugging a Vertex Shader -Since vertex shaders are run once per vertex in the draw call, it is easiest to find an invocation of the vertex shader in the geometry viewer. +Since vertex shaders are run once per vertex in the draw call, it is easiest to +find an invocation of the vertex shader in the geometry viewer. -In the `Bound Resources` of a specific draw call, open the `Geometry` section as described in the section on [inspecting a single draw call](#inspecting-a-single-draw-call). +In the `Bound Resources` of a specific draw call, open the `Geometry` section as +described in the section on [inspecting a single draw +call](#inspecting-a-single-draw-call). ![alt_text](assets/read_frame_captures/image14.png "image_tooltip") -In this view, the `Debug` button on the bottom right will be disabled unless a specific vertex in the geometry has been selected. Once you select the vertex whose vertex shader invocation you want to debug, the button should be enabled. Click it. - - +In this view, the `Debug` button on the bottom right will be disabled unless a +specific vertex in the geometry has been selected. Once you select the vertex +whose vertex shader invocation you want to debug, the button should be enabled. +Click it. ![alt_text](assets/read_frame_captures/image15.png "image_tooltip") -In the sidebar on the left, each step of execution of the vertex shader is listed. You can click on each to move back and forth through the invocation. The values of local variables will be updated as you do. +In the sidebar on the left, each step of execution of the vertex shader is +listed. You can click on each to move back and forth through the invocation. The +values of local variables will be updated as you do. Some of the usual things to look out for when debugging vertex shaders: - - -* Pay attention to the input to the vertex stage invocation. This is present in the argument marked with `[[stage_in]]`. This is the data you packed into the vertex buffer for the draw call using an `impeller::VertexBufferBuilder`. -* The output of the stage (which defines vertex position in normalized device coordinates) is the return value of the invocation. -* If you aren’t sure of a particular operation within the shader, try adding intermediate variables to the shader. The Impeller shader compiler will faithfully add those intermediate for ease of debugging. Optimizations that hinder debuggability are reserved for optimized release modes and occur on the intermediate representation. +* Pay attention to the input to the vertex stage invocation. This is present in + the argument marked with `[[stage_in]]`. This is the data you packed into the + vertex buffer for the draw call using an `impeller::VertexBufferBuilder`. +* The output of the stage (which defines vertex position in normalized device + coordinates) is the return value of the invocation. +* If you aren’t sure of a particular operation within the shader, try adding + intermediate variables to the shader. The Impeller shader compiler will + faithfully add those intermediate for ease of debugging. Optimizations that + hinder debuggability are reserved for optimized release modes and occur on the + intermediate representation. ### Live Shader Editing & Debugging -Often, it is useful to make minor edits to the shader to either visually see the difference in the attachments or to see how local variables are affected. +Often, it is useful to make minor edits to the shader to either visually see the +difference in the attachments or to see how local variables are affected. -When debugging an instrumentation of either the vertex or fragment shader, you have the ability to edit the Metal source code. When you do, the `Reload Shader` button at the bottom of the shader viewer that is typically disabled becomes enabled. +When debugging an instrumentation of either the vertex or fragment shader, you +have the ability to edit the Metal source code. When you do, the `Reload Shader` +button at the bottom of the shader viewer that is typically disabled becomes +enabled. ![alt_text](assets/read_frame_captures/image16.png "image_tooltip") -Click on that button to see what that invocation would look like had it used the updated shader. In the example above, I added an additional offset of 150 units to the vertex position supplied to the vertex shader by the vertex buffer. When I clicked on the `Reload Shaders` button, the location of the triangles in both the color and stencil attachments was updated. +Click on that button to see what that invocation would look like had it used the +updated shader. In the example above, I added an additional offset of 150 units +to the vertex position supplied to the vertex shader by the vertex buffer. When +I clicked on the `Reload Shaders` button, the location of the triangles in both +the color and stencil attachments was updated. -Unless you are only interested in inspecting local variables, it is often useful to have the attachments viewer open side-by-side as you make live updates to the shader. - -No changes are being made to your GLSL shaders in Impeller. This is purely a debugging aid and you must re-create those changes in GLSL to commit to those updates. +Unless you are only interested in inspecting local variables, it is often useful +to have the attachments viewer open side-by-side as you make live updates to the +shader. +No changes are being made to your GLSL shaders in Impeller. This is purely a +debugging aid and you must re-create those changes in GLSL to commit to those +updates. # Finding Where API Calls Were Made in the Codebase -From either the frame insights or by selecting an API call on the object, open the call-stack to navigate to the code that made that call. Then add your label. - +From either the frame insights or by selecting an API call on the object, open +the call-stack to navigate to the code that made that call. Then add your label. ![alt_text](assets/read_frame_captures/image17.png "image_tooltip") - -When inspecting an API call, reveal the call-stack. This resource has already been labelled and you’ll find the call in `AllocatorMTL::CreateTexture`. - +When inspecting an API call, reveal the call-stack. This resource has already +been labelled and you’ll find the call in `AllocatorMTL::CreateTexture`. ![alt_text](assets/read_frame_captures/image18.png "image_tooltip") -This trace-first approach of navigating an unfamiliar codebase is unreasonably effective. - +This trace-first approach of navigating an unfamiliar codebase is unreasonably +effective. # Next Steps & Further Reading - - -* Try repeating similar steps using a different profiler like RenderDoc or Android GPU Inspector. -* [Watch] WWDC 2018: [Metal Shader Debugging & Profiling](https://developer.apple.com/videos/play/wwdc2018/608/). +* Try repeating similar steps using a different profiler like RenderDoc or + Android GPU Inspector. +* [Watch] WWDC 2018: [Metal Shader Debugging & + Profiling](https://developer.apple.com/videos/play/wwdc2018/608/). diff --git a/engine/src/flutter/impeller/docs/ubo_gles2.md b/engine/src/flutter/impeller/docs/ubo_gles2.md index 61aabb5ea0b..ef6c93ab2c9 100644 --- a/engine/src/flutter/impeller/docs/ubo_gles2.md +++ b/engine/src/flutter/impeller/docs/ubo_gles2.md @@ -1,10 +1,29 @@ # How Impeller Works Around The Lack of Uniform Buffers in Open GL ES 2.0. -The Impeller Renderer API allows callers to specify uniform data in a discrete buffer. Indeed, it is conventional for the higher level layers of the Impeller stack to specify all uniform data for a render pass in a single buffer. This jumbo buffer is allocated using a simple bump allocator on the host before being transferred over to VRAM. The ImpellerC reflection engine generates structs with the correct padding and alignment such that the caller can just populate uniform struct members and `memcpy` them into the jumbo buffer, or use placement-new. Placement-new is used in cases where device buffers can be memory mapped into the client address space. +The Impeller Renderer API allows callers to specify uniform data in a discrete +buffer. Indeed, it is conventional for the higher level layers of the Impeller +stack to specify all uniform data for a render pass in a single buffer. This +jumbo buffer is allocated using a simple bump allocator on the host before being +transferred over to VRAM. The ImpellerC reflection engine generates structs with +the correct padding and alignment such that the caller can just populate uniform +struct members and `memcpy` them into the jumbo buffer, or use placement-new. +Placement-new is used in cases where device buffers can be memory mapped into +the client address space. -This works extremely well when using a modern rendering backend like Metal. However, OpenGL ES 2.0 does not support uniform buffer objects. Instead, uniform data must be specified to GL from the client side using the `glUniform*` family of APIs. This poses a problem for the OpenGL backend implementation. From a view (an offset and range) into a buffer pointing to uniform data, it must infer the right uniform locations within a program object and bind uniform data at the right offsets within the buffer view. +This works extremely well when using a modern rendering backend like Metal. +However, OpenGL ES 2.0 does not support uniform buffer objects. Instead, uniform +data must be specified to GL from the client side using the `glUniform*` family +of APIs. This poses a problem for the OpenGL backend implementation. From a view +(an offset and range) into a buffer pointing to uniform data, it must infer the +right uniform locations within a program object and bind uniform data at the +right offsets within the buffer view. -Since command generation is strongly typed, a pointer to metadata about the uniform information is stashed along with the buffer view in the command stream. This metadata is generated by the offline reflection engine part of ImpellerC. The metadata is usually a collection of items the runtime would need to infer the right `glUniform*` calls. An item in this collection would look like the following: +Since command generation is strongly typed, a pointer to metadata about the +uniform information is stashed along with the buffer view in the command stream. +This metadata is generated by the offline reflection engine part of ImpellerC. +The metadata is usually a collection of items the runtime would need to infer +the right `glUniform*` calls. An item in this collection would look like the +following: ``` struct ShaderStructMemberMetadata { @@ -15,10 +34,29 @@ struct ShaderStructMemberMetadata { }; ``` -Using this mechanism, the runtime knows how to specify data from a buffer view to GL. But, this is still not sufficient as the buffer bindings are not known until after program link time. +Using this mechanism, the runtime knows how to specify data from a buffer view +to GL. But, this is still not sufficient as the buffer bindings are not known +until after program link time. -To solve this issue, Impeller queries all active uniforms after program link time using `glGet` with `GL_ACTIVE_UNIFORMS`. It then iterates over these uniforms and notes their location using `glGetUniformLocation`. This uniform location in the program is mapped to the reflection engine's notion of the uniform location in the pipeline. This mapping is maintained in the pipeline state generated once during the Impeller runtime setup. In this way, even though there is no explicit notion of a pipeline state object in OpenGL ES, Impeller still maintains one for this backend. +To solve this issue, Impeller queries all active uniforms after program link +time using `glGet` with `GL_ACTIVE_UNIFORMS`. It then iterates over these +uniforms and notes their location using `glGetUniformLocation`. This uniform +location in the program is mapped to the reflection engine's notion of the +uniform location in the pipeline. This mapping is maintained in the pipeline +state generated once during the Impeller runtime setup. In this way, even though +there is no explicit notion of a pipeline state object in OpenGL ES, Impeller +still maintains one for this backend. -Since all commands in the command stream reference the pipeline state object associated with the command, the render pass implementation in the OpenGL ES 2 backend can access the uniform bindings map and use that to bind uniforms using the pointer to metadata already located next to the commands' uniform buffer views. +Since all commands in the command stream reference the pipeline state object +associated with the command, the render pass implementation in the OpenGL ES 2 +backend can access the uniform bindings map and use that to bind uniforms using +the pointer to metadata already located next to the commands' uniform buffer +views. -And that’s it. This is convoluted in its implementation, but the higher levels of the tech stack don’t have to care about not having access to uniform buffer objects. Moreover, all the reflection happens offline and reflection information is specified in the command stream via just a pointer. The uniform bindings map is also generated just once during the setup of the faux pipeline state object. This makes the whole scheme extremely low overhead. Moreover, in a modern backend with uniform buffers, this mechanism is entirely irrelevant. +And that’s it. This is convoluted in its implementation, but the higher levels +of the tech stack don’t have to care about not having access to uniform buffer +objects. Moreover, all the reflection happens offline and reflection information +is specified in the command stream via just a pointer. The uniform bindings map +is also generated just once during the setup of the faux pipeline state object. +This makes the whole scheme extremely low overhead. Moreover, in a modern +backend with uniform buffers, this mechanism is entirely irrelevant. diff --git a/engine/src/flutter/impeller/docs/xcode_frame_capture.md b/engine/src/flutter/impeller/docs/xcode_frame_capture.md index c58cac941a3..e258f022ff6 100644 --- a/engine/src/flutter/impeller/docs/xcode_frame_capture.md +++ b/engine/src/flutter/impeller/docs/xcode_frame_capture.md @@ -1,112 +1,113 @@ # Setup Xcode for GPU Frame Capture -Xcode needs to be able to attach to a process to be able to instrument and profile it. +Xcode needs to be able to attach to a process to be able to instrument and +profile it. -As you are working on Impeller, it is easiest to set up an Xcode project just to bring Impeller artifacts up-to-date, launch the required test binary, enable Metal validation layer, and either capture a GPU frame or instrument the test case with Instruments. - -If you are already somewhat familiar with Xcode, none of this is new. If not, follow along. +As you are working on Impeller, it is easiest to set up an Xcode project just to +bring Impeller artifacts up-to-date, launch the required test binary, enable +Metal validation layer, and either capture a GPU frame or instrument the test +case with Instruments. +If you are already somewhat familiar with Xcode, none of this is new. If not, +follow along. # Set Up an Xcode Project for Instrumentation In Xcode, `File -> New -> Project…`, select an empty project. - ![alt_text](assets/xcode_frame_capture/image1.png "image_tooltip") - -Call it whatever you want, you are not going to check this into version control and the targets are going to be specific to your workflow. - - +Call it whatever you want, you are not going to check this into version control +and the targets are going to be specific to your workflow. ![alt_text](assets/xcode_frame_capture/image2.png "image_tooltip") +Save it outside the source tree. Since you are not going to check this in, you +don’t want to accidentally delete it via a `git clean -fdx` when regenerating +the licenses (ask me how I know). -Save it outside the source tree. Since you are not going to check this in, you don’t want to accidentally delete it via a `git clean -fdx` when regenerating the licenses (ask me how I know). - -Create a new `External Build System` target in the empty project by clicking on the `+` icon at the bottom. - - +Create a new `External Build System` target in the empty project by clicking on +the `+` icon at the bottom. ![alt_text](assets/xcode_frame_capture/image3.png "image_tooltip") - -Click through the defaults (it wants you to use `make`) to create the target, we are going to be modifying it later. - +Click through the defaults (it wants you to use `make`) to create the target, we +are going to be modifying it later. ![alt_text](assets/xcode_frame_capture/image4.png "image_tooltip") - -Select the target you just created from the sidebar and in the `Info` tab, fill in the command you would use to bring the target up-to-date. In the example, I am building the Impeller unit-tests. - - +Select the target you just created from the sidebar and in the `Info` tab, fill +in the command you would use to bring the target up-to-date. In the example, I +am building the Impeller unit-tests. ![alt_text](assets/xcode_frame_capture/image5.png "image_tooltip") +If you wanted to instrument multiple targets and switch between them, you would +add them here. -If you wanted to instrument multiple targets and switch between them, you would add them here. - -Xcode still doesn’t know how to launch the executables generated by the targets however. You need to specify a Run Scheme for that. We’ll do that next. +Xcode still doesn’t know how to launch the executables generated by the targets +however. You need to specify a Run Scheme for that. We’ll do that next. Click the default scheme for the target. - - ![alt_text](assets/xcode_frame_capture/image6.png "image_tooltip") - In the Pop-Up, click `Edit Scheme`. - - ![alt_text](assets/xcode_frame_capture/image7.png "image_tooltip") - -In the Info tab, select the executable you want to launch after the target has been updated by clicking on `Other…`. - +In the Info tab, select the executable you want to launch after the target has +been updated by clicking on `Other…`. ![alt_text](assets/xcode_frame_capture/image8.png "image_tooltip") - I want to launch the unit-tests harness. Select it in the `out` directory. - - ![alt_text](assets/xcode_frame_capture/image9.png "image_tooltip") - -Now, when you click `Product -> Run` in Xcode, the unit-tests target will be brought up to data and run. - +Now, when you click `Product -> Run` in Xcode, the unit-tests target will be +brought up to data and run. # Enabling Metal Validation & GPU Frame Capture -Xcode doesn’t know that the executable you are trying to instrument is Metal enabled. You just asked it to launch a random executable it knows nothing about. - -In the `Options` tab on the `Edit Scheme…` pop-up, in the `GPU Frame Capture` section, set API detection to `Metal` and check `Profile GPU trace after capture`. - +Xcode doesn’t know that the executable you are trying to instrument is Metal +enabled. You just asked it to launch a random executable it knows nothing about. +In the `Options` tab on the `Edit Scheme…` pop-up, in the `GPU Frame Capture` +section, set API detection to `Metal` and check `Profile GPU trace after +capture`. ![alt_text](assets/xcode_frame_capture/image10.png "image_tooltip") - -Then, in the `Diagnostics` tab on the `Edit Scheme…` pop-up, in the `Metal` section, enable `API Validation` and `Shader Validation`. - +Then, in the `Diagnostics` tab on the `Edit Scheme…` pop-up, in the `Metal` +section, enable `API Validation` and `Shader Validation`. ![alt_text](assets/xcode_frame_capture/image11.png "image_tooltip") +On a side note, you may be tempted to enable all the other diagnostics. Be aware +that some of those diagnostics need Xcode to be able to re-compile the +translation units in the engine. But, it doesn’t know how to do that, only +GN/Ninja does. So some of those will be unavailable. -On a side note, you may be tempted to enable all the other diagnostics. Be aware that some of those diagnostics need Xcode to be able to re-compile the translation units in the engine. But, it doesn’t know how to do that, only GN/Ninja does. So some of those will be unavailable. - -Any Impeller test that setup up a Playground will now automatically have GPU frame capture enabled. - +Any Impeller test that setup up a Playground will now automatically have GPU +frame capture enabled. # Select the Playground Enabled Test to Profile -Tests that launch Playground instances are just Google Test cases. You just need to [pass the right Google Test flags](https://google.github.io/googletest/advanced.html#running-a-subset-of-the-tests) to the running executable as command line arguments. +Tests that launch Playground instances are just Google Test cases. You just need +to [pass the right Google Test +flags](https://google.github.io/googletest/advanced.html#running-a-subset-of-the-tests) +to the running executable as command line arguments. -To do this, in the `Options` tab on the `Edit Scheme…` pop-up, in the `Arguments Passed on Launch` section, add the right `--gtest_filter=` to launch, and instrument just the one test you want. +To do this, in the `Options` tab on the `Edit Scheme…` pop-up, in the `Arguments +Passed on Launch` section, add the right `--gtest_filter=` to launch, and +instrument just the one test you want. ![alt_text](assets/xcode_frame_capture/image12.png "image_tooltip") - -This is also the spot where you will add other command line arguments that will aid in your debugging. In that example, `–timeout=-1` will disable the Flutter test hang watchdog which will kill your process if the test doesn’t complete in 30 seconds. I also like to set the observatory port to a known value so I can get to it and disable service auth codes so I can just refresh the page to launch the latest version of the observatory. +This is also the spot where you will add other command line arguments that will +aid in your debugging. In that example, `–timeout=-1` will disable the Flutter +test hang watchdog which will kill your process if the test doesn’t complete in +30 seconds. I also like to set the observatory port to a known value so I can +get to it and disable service auth codes so I can just refresh the page to +launch the latest version of the observatory.