summaryrefslogtreecommitdiff
path: root/src/intel/vulkan/anv_pipeline.c
AgeCommit message (Collapse)AuthorFilesLines
2017-03-24anv/pipeline: make FragCoord include sample positions when sample shadingIago Toral Quiroga1-8/+18
We need to know if sample shading has been requested during shader compilation since that affects the way fragment coordinates are computed. Notice that the semantics of fragment coordinates only depend on whether sample shading has been requested, not on whether more than one sample will actually be produced (that is, minSampleShading and rasterizationSamples do not affect this behavior). Because this setting affects the code we generate for the shader, we also need to include it in the WM prog key. Notice we don't need to alter the OpenGL code because it doesn't ever use this behavior, so they key's value is always false (the default). Fixes: dEQP-VK.glsl.builtin_var.fragcoord_msaa.* Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-03-24nir/lower_wpos_center: support adding sample position to fragment coordinateIago Toral Quiroga1-1/+1
According to section 14.6 of the Vulkan specification: "When sample shading is enabled, the x and y components of FragCoord reflect the location of the sample corresponding to the shader invocation." So add a boolean parameter to the lowering pass to select this behavior when we need it. Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-03-16anv: improve error reporting when creating pipelinesIago Toral Quiroga1-0/+1
Specifically, report 'out of memory' errors that might have happened while emitting the pipeline's batch. Reviewed-by: Topi Pohjolainen <topi.pohjolainen@intel.com>
2017-03-13anv: Use on-the-fly surface states for dynamic buffer descriptorsJason Ekstrand1-6/+0
We have a performance problem with dynamic buffer descriptors. Because we are currently implementing them by pushing an offset into the shader and adding that offset onto the already existing offset for the UBO/SSBO operation, all UBO/SSBO operations on dynamic descriptors are indirect. The back-end compiler implements indirect pull constant loads using what basically amounts to a texelFetch instruction. For pull constant loads with constant offsets, however, we use an oword block read message which goes through the constant cache and reads a whole cache line at a time. Because of these two things, direct pull constant loads are much faster than indirect pull constant loads. Because all loads from dynamically bound buffers are indirect, the user takes a substantial performance penalty when using this "performance" feature. There are two potential solutions I have seen for this problem. The alternate solution is to continue pushing offsets into the shader but wire things up in the back-end compiler so that we use the oword block read messages anyway. The only reason we can do this because we know a priori that the dynamic offsets are uniform and 16-byte aligned. Unfortunately, thanks to the 16-byte alignment requirement of the oword messages, we can't do some general "if the indirect offset is uniform, use an oword message" sort of thing. This solution, however, is recommended for a few of reasons: 1. Surface states are relatively cheap. We've been using on-the-fly surface state setup for some time in GL and it works well. Also, dynamic offsets with on-the-fly surface state should still be cheaper than allocating new descriptor sets every time you want to change a buffer offset which is really the only requirement of the dynamic offsets feature. 2. This requires substantially less compiler plumbing. Not only can we delete the entire apply_dynamic_offsets pass but we can also avoid having to add architecture for passing dynamic offsets to the back- end compiler in such a way that it can continue using oword messages. 3. We get robust buffer access range-checking for free. Because the offset and range are baked into the surface state, we no longer need to pass ranges around and do bounds-checking in the shader. 4. Once we finally get UBO pushing implemented, it will be much easier to handle pushing chunks of dynamic descriptors if the compiler remains blissfully unaware of dynamic descriptors. This commit improves performance of The Talos Principle on ULTRA settings by around 50% and brings it nicely into line with OpenGL performance. Reviewed-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
2017-03-13i965: Move the back-end compiler to src/intel/compilerJason Ekstrand1-1/+1
Mostly a dummy git mv with a couple of noticable parts: - With the earlier header cleanups, nothing in src/intel depends files from src/mesa/drivers/dri/i965/ - Both Autoconf and Android builds are addressed. Thanks to Mauro and Tapani for the fixups in the latter - brw_util.[ch] is not really compiler specific, so it's moved to i965. v2: - move brw_eu_defines.h instead of brw_defines.h - remove no-longer applicable includes - add missing vulkan/ prefix in the Android build (thanks Tapani) v3: - don't list brw_defines.h in src/intel/Makefile.sources (Jason) - rebase on top of the oa patches [Emil Velikov: commit message, various small fixes througout] Signed-off-by: Emil Velikov <emil.velikov@collabora.com> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-03-07anv: Drop the anv_validate block helperJason Ekstrand1-3/+3
Over the course of driver development, we've come up with a number of different schemes for adding giant blocks of asserts inside the driver. This one is only being used once in anv_pipeline.c and the way it's being used actually generates compiler warnings in release builds. This commit drops the anv_validate macro and just puts the contents of the one validation function in side of a "#ifdef DEBUG" guard. Reviewed-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
2017-03-07anv: Remove a pointless finishmeJason Ekstrand1-4/+0
We've been supporting multiple shaders per module for some time now. Reviewed-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
2017-03-03anv: Advertise shaderInt64 on Broadwell and aboveJason Ekstrand1-0/+1
Reviewed-by: Samuel Iglesias Gonsálvez <siglesias@igalia.com>
2017-03-02anv: Store the user's VkAttachmentReferenceNanley Chery1-3/+3
We will be using the image layout. Store the full struct directly from the user. Signed-off-by: Nanley Chery <nanley.g.chery@intel.com> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-02-23anv: Enable MSAA compressionJason Ekstrand1-0/+19
This just enables basic MSAA compression (no fast clears) for all multisampled surfaces. This improves the framerate of the Sascha "multisampling" demo by 76% on my Sky Lake laptop. Running Talos on medium settings with 8x MSAA, this improves the framerate in the benchmark by 80%. Reviewed-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com> Reviewed-by: Chad Versace <chadversary@chromium.org>
2017-02-14anv: Add support for shaderStorageImageWriteWithoutFormatAlex Smith1-0/+1
This allows shaders to write to storage images declared with unknown format if they are decorated with NonReadable ("writeonly" in GLSL). Previously an image view would always use a lowered format for its surface state, however when a shader declares a write-only image, we should use the real format. Since we don't know at view creation time whether it will be used with only write-only images in shaders, create two surface states using both the original format and the lowered format. When emitting the binding table, choose between the states based on whether the image is declared write-only in the shader. Tested on both Sascha Willems' computeshader sample (with the original shaders and ones modified to declare images writeonly and omit their format qualifiers) and on our own shaders for which we need support for this. Signed-off-by: Alex Smith <asmith@feralinteractive.com> Reviewed-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-02-02anv: enable VK_KHR_shader_draw_parametersLionel Landwerlin1-0/+1
Enables 10 tests from: dEQP-VK.draw.shader_draw_parameters.* Signed-off-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com> Reviewed-by: Anuj Phogat <anuj.phogat@gmail.com> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-01-13anv/pipeline: Replace get_fs_input_map with get_last_vue_prog_dataJason Ekstrand1-1/+1
This lets us delete a helper from genX_pipeline.c Reviewed-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
2017-01-13anv: Move nir_lower_wpos_center after dead variable elimination.Kenneth Graunke1-3/+3
When multiple shader stages exist in the same SPIR-V module, we compile all entry points and their inputs/outputs, then dead code eliminate the ones not related to the specific entry point later. nir_lower_wpos_center was being run prior to eliminating those random other variables, which made it trip up, thinking it found gl_FragCoord when it actually found something else like gl_PerVertex[3]. Fixes dEQP-VK.spirv_assembly.instruction.graphics.module.same_module. Signed-off-by: Kenneth Graunke <kenneth@whitecape.org> Reviewed-by: Timothy Arceri <timothy.arceri@collabora.com> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-01-10anv: Enable tessellation shaders.Kenneth Graunke1-0/+1
Signed-off-by: Kenneth Graunke <kenneth@whitecape.org> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-01-10anv: Compile TCS/TES shaders.Kenneth Graunke1-2/+191
v2: Merge more TCS/TES info. v3: Fix caching keys. Signed-off-by: Kenneth Graunke <kenneth@whitecape.org> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-01-10anv: Handle patch primitives.Kenneth Graunke1-2/+7
v2: Use anv_pipeline_has_stage rather than tess_info != NULL. Signed-off-by: Kenneth Graunke <kenneth@whitecape.org> Reviewed-by: Iago Toral Quiroga <itoral@igalia.com> [v1] Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-01-10anv: set input_slots_valid on brw_wm_prog_keyLionel Landwerlin1-3/+8
With shaders using a lot of inputs/outputs, like this (from Gtk+) : layout(location = 0) in vec2 inPos; layout(location = 1) in float inGradientPos; layout(location = 2) in flat int inRepeating; layout(location = 3) in flat int inStopCount; layout(location = 4) in flat vec4 inClipBounds; layout(location = 5) in flat vec4 inClipWidths; layout(location = 6) in flat ColorStop inStops[8]; layout(location = 0) out vec4 outColor; we're missing the programming of the input_slots_valid field leading to an assert further down the backend code. v2: Use valid slots of the geometry or vertex stage (Jason) v3: Use helper to find correct vue map (Jason) v4: Set the valid slots off the previous stages (Jason) Signed-off-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-01-09anv: enable float64 feature on supported platformsSamuel Iglesias Gonsálvez1-1/+5
v2: - Remove image_ms_array initialization (Jason) Signed-off-by: Samuel Iglesias Gonsálvez <siglesias@igalia.com> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-01-09anv/pipeline: get map for double input attributesJuan A. Suarez Romero1-0/+1
Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-01-09spirv: add support for doubles to OpSpecConstantSamuel Iglesias Gonsálvez1-1/+4
v2 (Jason): - Fix indent in radv change - Add vtn_u64_literal() helper to take 64 bits (Jason) Signed-off-by: Samuel Iglesias Gonsálvez <siglesias@igalia.com> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2017-01-07anv/pipeline: Call NIR passes using NIR_PASS_VJason Ekstrand1-31/+15
This lets us get validation without having to do it manually. Reviewed-by: Timothy Arceri <timothy.arceri@collabora.com>
2017-01-07anv/pipeline: Only call remove_dead_variables onceJason Ekstrand1-3/+3
It can handle multiple modes at a time now so there's no reason to call it repeatedly. Reviewed-by: Timothy Arceri <timothy.arceri@collabora.com>
2016-12-28spirv: add interface for drivers to define support extensions.Dave Airlie1-1/+1
I expect over time the struct contents will change as all drivers support stuff etc, but for now this should be a good starting point. Reviewed-by: Edward O'Callaghan <funfunctor@folklore1984.net> Reviewed-by: Bas Nieuwenhuizen <bas@basnieuwenhuizen.nl> Acked-by: Jason Ekstrand <jason@jlekstrand.net> Signed-off-by: Dave Airlie <airlied@redhat.com>
2016-12-23i965: use nir_lower_indirect_derefs() for GLSLTimothy Arceri1-10/+0
This moves the nir_lower_indirect_derefs() call into brw_preprocess_nir() so thats is called by both OpenGL and Vulkan and removes that call to the old GLSL IR pass lower_variable_index_to_cond_assign() We want to do this pass in nir to be able to move loop unrolling to nir. There is a increase of 1-3 instructions in a small number of shaders, and 2 Kerbal Space program shaders that increase by 32 instructions. The changes seem to be caused be the difference in the GLSL IR vs NIR variable index lowering passes. The GLSL IR pass creates a simple if ladder for arrays of size 4 or less, while the NIR pass implements a binary search for all arrays regardless of size. Shader-db results BDW: total instructions in shared programs: 13021176 -> 13021819 (0.00%) instructions in affected programs: 57693 -> 58336 (1.11%) helped: 20 HURT: 190 total cycles in shared programs: 299805580 -> 299750826 (-0.02%) cycles in affected programs: 2290024 -> 2235270 (-2.39%) helped: 337 HURT: 442 total fills in shared programs: 19984 -> 19984 (0.00%) fills in affected programs: 0 -> 0 helped: 0 HURT: 0 LOST: 4 GAINED: 0 V2: remove the do_copy_propagation() call from the i965 GLSL IR linking code. This call was added in f7741c52111 but since we are moving the variable index lowering to NIR we no longer need it and can just rely on the nir copy propagation pass. Reviewed-by: Kenneth Graunke <kenneth@whitecape.org> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2016-12-05anv/pipeline: Call nir_lower_constant_initializersJason Ekstrand1-0/+13
Reviewed-by: Iago Toral Quiroga <itoral@igalia.com>
2016-12-05Revert "i965: use nir_lower_indirect_derefs() for GLSL"Jason Ekstrand1-0/+10
This reverts commit 9404439a754e5640ccd98df40fa694835c0d8759. I didn't intend to push it and it breaks clip and cull distance.
2016-12-05i965: use nir_lower_indirect_derefs() for GLSLTimothy Arceri1-10/+0
This moves the nir_lower_indirect_derefs() call into brw_preprocess_nir() so thats is called by both OpenGL and Vulkan and removes that call to the old GLSL IR pass lower_variable_index_to_cond_assign() We want to do this pass in nir to be able to move loop unrolling to nir. There is a increase of 1-3 instructions in a small number of shaders, and 2 Kerbal Space program shaders that increase by 32 instructions. Shader-db results BDW: total instructions in shared programs: 8705873 -> 8706194 (0.00%) instructions in affected programs: 32515 -> 32836 (0.99%) helped: 3 HURT: 79 total cycles in shared programs: 74618120 -> 74583476 (-0.05%) cycles in affected programs: 528104 -> 493460 (-6.56%) helped: 47 HURT: 37 LOST: 2 GAINED: 0
2016-11-22anv: Add an input attachment lowering passJason Ekstrand1-0/+3
Reviewed-by: Jordan Justen <jordan.l.justen@intel.com>
2016-11-22anv: Combine ClipDistance and CullDistance arrays.Kenneth Graunke1-0/+3
Signed-off-by: Kenneth Graunke <kenneth@whitecape.org> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2016-11-16anv: Handle null in all destructorsJason Ekstrand1-0/+6
This fixes a bunch of new CTS tests which look for exactly this. Even in the cases where we just call vk_free to free a CPU data structure, we still handle NULL explicitly. This way we're less likely to forget to handle NULL later should we actually do something less trivial. Cc: "13.0" <mesa-stable@lists.freedesktop.org> Reviewed-by: Dave Airlie <airlied@redhat.com>
2016-11-16anv/pipeline: Get rid of the kernel pointer fieldsJason Ekstrand1-22/+0
Now that we have anv_shader_bin, they're completely redundant with other information we have in the pipeline. For vertex shaders, we also go through way too much work to put the offset in one or the other field and then look at which one we put it in later. Reviewed-by: Timothy Arceri <timothy.arceri@collabora.com> Reviewed-by: Kristian H. Kristensen <hoegsberg@google.com>
2016-11-02anv/pipeline: Properly cache prog_data::paramJason Ekstrand1-1/+2
Before we were caching the prog data but we weren't doing anything with brw_stage_prog_data::param so anything with push constants wasn't getting cached properly. This commit fixes that. Signed-off-by: Jason Ekstrand <jason@jlekstrand.net> Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=98012 Reviewed-by: Timothy Arceri <timothy.arceri@collabora.com> Cc: "13.0" <mesa-stable@lists.freedesktop.org>
2016-11-02anv/pipeline: Put actual pointers in anv_shader_binJason Ekstrand1-6/+11
While we can simply calculate offsets to get to things such as the prog_data and the key, it's much more user-friendly if there are just pointers. Also, it's a bit more fool-proof. While we're at it, we rework the pipeline cache API to use the brw_stage_prog_data type directly. Signed-off-by: Jason Ekstrand <jason@jlekstrand.net> Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=98012 Reviewed-by: Topi Pohjolainen <topi.pohjolainen@intel.com> Reviewed-by: Timothy Arceri <timothy.arceri@collabora.com> Cc: "13.0" <mesa-stable@lists.freedesktop.org>
2016-10-26i965: rewrite brw_setup_vue_interpolation()Timothy Arceri1-1/+1
Here brw_setup_vue_interpolation() is rewritten not to use the InterpQualifier array in gl_fragment_program which will allow us to remove it. This change also makes the code which is only used by gen4/5 more self contained as it now has its own gen5_fragment_program struct rather than storing the map in brw_context. This means the interpolation map will only get processed once and will get stored in the in memory cache rather than being processed everytime the fs changes. Also by calling this from the fs compile code rather than from the upload code and using the interpolation assigned there we can get rid of the BRW_NEW_INTERPOLATION_MAP flag. It might not seem ideal to add a gen5_fragment_program struct however by the end of this series we will have gotten rid of all the brw_{shader_stage}_program structs and replaced them with a generic brw_program struct so there will only be two program structs which is better than what we have now. V2: Don't remove BRW_NEW_INTERPOLATION_MAP from dirty_bit_map until the following patch to fix build error. V3 - Suggestions by Jason: - name struct gen4_fragment_program rather than gen5_fragment_program - don't use enum with memset() - create interp mode set helper and simplify logic to call it - add assert when calling function to show prog will never be NULL for gen4/5 i.e. no Vulkan Reviewed-by: Jason Ekstrand <jason@jlekstrand.net> Reviewed-by: Kenneth Graunke <kenneth@whitecape.org>
2016-10-26nir/i965/anv/radv/gallium: make shader info a pointerTimothy Arceri1-9/+9
When restoring something from shader cache we won't have and don't want to create a nir_shader this change detaches the two. There are other advantages such as being able to reuse the shader info populated by GLSL IR. Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2016-10-19anv: move to using vk_alloc helpers.Dave Airlie1-3/+3
This moves all the alloc/free in anv to the generic helpers. Acked-by: Jason Ekstrand <jason@jlekstrand.net> Signed-off-by: Dave Airlie <airlied@redhat.com>
2016-10-14anv: Move Create*Pipelines into genX_cmd_buffer.cJason Ekstrand1-106/+0
Now that we don't have meta, we have no need for a gen-agnostic pipeline create path. We can, instead, just generate one Create*Pipelines function per gen and be done with it. Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
2016-10-14anv/pipeline: Remove support for direct-from-nir shadersJason Ekstrand1-73/+54
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
2016-10-14anv: Get rid of graphics_pipeline_create_info_extraJason Ekstrand1-43/+12
Now that we no longer have meta, all pipelines get created via the normal Vulkan pipeline creation mechanics. There is no more need for this bit of extra magic data that we've been passing around. Signed-off-by: Jason Ekstrand <jason@jlekstrand.net> Reviewed-by: Topi Pohjolainen <topi.pohjolainen@intel.com>
2016-10-14anv: Get rid of metaJason Ekstrand1-4/+2
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net> Reviewed-by: Topi Pohjolainen <topi.pohjolainen@intel.com>
2016-10-11anv: Fix anv_pipeline_validate_create_info assertions.Kenneth Graunke1-7/+9
Many of these can be "NULL if the pipeline has rasterization disabled." Also, we should assert that pMultisampleState exists. Signed-off-by: Kenneth Graunke <kenneth@whitecape.org> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
2016-09-13anv/pipeline: Roll compute_urb_partition into emit_urb_setupJason Ekstrand1-142/+0
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net> Reviewed-by: Topi Pohjolainen <topi.pohjolainen@intel.com>
2016-09-06anv/pipeline: Lower indirect outputs when EmitNoIndirectOutput is setJason Ekstrand1-0/+2
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net> Reported-by: Timothy Arceri <timothy.arceri@collabora.com> Reviewed-by: Kenneth Graunke <kenneth@whitecape.org>
2016-09-03anv: Refactor pipeline l3 config setupJason Ekstrand1-24/+22
Now that we're using gen_l3_config.c, we no longer have one set of l3 config functions per gen and we can simplify a bit. Also, we know that only compute uses SLM so we don't need to look for it in all of the stages. Signed-off-by: Jason Ekstrand <jason@jlekstrand.net> Reviewed-by: Jordan Justen <jordan.l.justen@intel.com>
2016-09-03intel: s/brw_device_info/gen_device_info/Jason Ekstrand1-7/+7
Generated by: sed -i -e 's/brw_device_info/gen_device_info/g' src/intel/**/*.c sed -i -e 's/brw_device_info/gen_device_info/g' src/intel/**/*.h sed -i -e 's/brw_device_info/gen_device_info/g' **/i965/*.c sed -i -e 's/brw_device_info/gen_device_info/g' **/i965/*.cpp sed -i -e 's/brw_device_info/gen_device_info/g' **/i965/*.h Signed-off-by: Jason Ekstrand <jason@jlekstrand.net> Reviewed-by: Jordan Justen <jordan.l.justen@intel.com>
2016-08-30anv: Rework pipeline cachingJason Ekstrand1-70/+91
The original pipeline cache the Kristian wrote was based on a now-false premise that the shaders can be stored in the pipeline cache. The Vulkan 1.0 spec explicitly states that the pipeline cache object is transiant and you are allowed to delete it after using it to create a pipeline with no ill effects. As nice as Kristian's design was, it doesn't jive with the expectation provided by the Vulkan spec. The new pipeline cache uses reference-counted anv_shader_bin objects that are backed by a large state pool. The cache itself is just a hash table mapping keys hashes to anv_shader_bin objects. This has the added advantage of removing one more hand-rolled hash table from mesa. Signed-off-by: Jason Ekstrand <jason@jlekstrand.net> Cc: "12.0" <mesa-stable@lists.freedesktop.org> Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=97476 Acked-by: Kristian Høgsberg Kristensen <krh@bitplanet.net>
2016-08-30anv/pipeline: Properly handle OOM during shader compilationJason Ekstrand1-12/+23
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net> Cc: "12.0" <mesa-stable@lists.freedesktop.org>
2016-08-30anv/pipeline: Fix bind maps for fragment output arraysJason Ekstrand1-1/+1
Found by inspection. Signed-off-by: Jason Ekstrand <jason@jlekstrand.net> Cc: "12.0" <mesa-stable@lists.freedesktop.org>
2016-08-25nir: Change nir_shader_get_entrypoint to return an impl.Kenneth Graunke1-2/+3
Jason suggested adding an assert(function->impl) here. All callers of this function actually want ->impl, so I decided just to change the API. We also change the nir_lower_io_to_temporaries API here. All but one caller passed nir_shader_get_entrypoint(), and with the previous commit, it now uses a nir_function_impl internally. Folding this change in avoids the need to change it and change it back. v2: Fix one call I missed in ir3_compiler (caught by Eric). Signed-off-by: Kenneth Graunke <kenneth@whitecape.org> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net> Reviewed-by: Connor Abbott <cwabbott0@gmail.com>