Compare commits

..

216 commits

Author SHA1 Message Date
Alex Janka c56b0a7701 shaders can be either a path or a string 2024-10-23 14:01:09 +11:00
Alex Janka 04113b66f7 nightly rust-toolchain.toml 2024-10-23 13:41:08 +11:00
chyyran cbdbdafecd chore: Release 2024-10-06 16:07:06 -04:00
Ronny Chan ffcb8ba1d1
doc: Update BROKEN_SHADERS.md
[skip ci]
2024-10-06 15:44:05 -04:00
chyyran 941275199b build: unpin cc 2024-10-06 15:32:18 -04:00
chyyran 525c311844 cli: allow raw-id for spirv output 2024-10-06 15:32:18 -04:00
chyyran 2fe66d958f reflect: put vars to end with running link pass to fix naga ordering 2024-10-06 15:31:34 -04:00
chyyran ed8bf637a9 cli: size_of not in prelude 2024-10-06 13:13:19 -04:00
chyyran 7555bb9024 build: make librashader-reflect features-table more consistent 2024-10-06 12:49:47 -04:00
chyyran 562e1e5f18 doc(msrv): bump msrv to 1.78
Available in debian 12 as rustc-web
2024-10-06 03:06:41 -04:00
chyyran 13b44e1639 cargo: revert lockfile version 2024-10-06 03:04:04 -04:00
chyyran 240aae1bcf chore: Release 2024-10-06 01:05:35 -04:00
chyyran 95a489ee12 cli(render): allow specifying output dimensions 2024-10-05 23:12:25 -04:00
chyyran 4abd48eb24 capi(presets): support d3d9_hlsl VID-DRV 2024-10-03 00:26:59 -04:00
chyyran 0007bd6d98 doc: update preset_ctx_set_runtime doc 2024-10-03 00:22:33 -04:00
chyyran 72c72dafa0 capi(d3d12): remove unintentional IMAGE_TYPE infix for LIBRA_D3D12_IMAGE_TYPE 2024-10-03 00:18:50 -04:00
chyyran 0fde712f3c doc: update librashader::reflect docs 2024-10-02 19:07:09 -04:00
chyyran 2323b18710 capi(ld): add LIBRA_RUNTIME_D3D9 define commented out 2024-10-02 19:07:09 -04:00
chyyran 5978f95f76 reflect: make ShaderPassArtifact simpler as PassResource already includes the source data 2024-10-02 19:07:09 -04:00
chyyran 3ee5e66c0d presets: make naming more consistent
* `ShaderPassConfig` -> `PassConfig`
* `ShaderPassData` -> `PassResource`
* `TextureData` -> `TextureResource`
* `ShaderPresetResource` -> `LoadedResource`
* `ShaderPassMeta` -> `PassMeta`

* `ShaderPreset::shaders` -> `ShaderPreset::passes`
* `ShaderPreset::shader_count` -> `ShaderPreset::pass_count`

* `ShaderPresetPack::shaders` -> `ShaderPresetPack::passes`
* `ShaderPresetPack::shader_count` -> `ShaderPresetPack::pass_count`
2024-10-02 19:07:09 -04:00
chyyran f189468f6d ci: don't build cli for windows 7 2024-10-02 19:07:09 -04:00
chyyran fc47fb4c5a doc: typo 2024-10-02 01:09:23 -04:00
chyyran fc7739d9ab ci: update github actions 2024-10-02 01:02:46 -04:00
chyyran 4a8a9ee444 chore: Release 2024-10-02 00:28:10 -04:00
chyyran 756cbe63d9 cli: fix cli Cargo.toml 2024-10-02 00:27:11 -04:00
chyyran 796b03b42b test: rework tests to work with ShaderPresetPack 2024-10-02 00:16:11 -04:00
chyyran 742a1c8658 dep: update glslang 2024-10-01 23:59:29 -04:00
chyyran 7c03a7e3fe doc(pack): reword doc on ShaderPresetPack 2024-10-01 23:25:52 -04:00
chyyran 4ba6effc2f rt(mtl): fix test 2024-10-01 23:25:52 -04:00
chyyran 32c99d9f4a rt: implement filter chain loading in terms of pack 2024-10-01 23:25:52 -04:00
chyyran 75b70cc0e6 cli: add pack command to create a preset pack 2024-10-01 23:25:52 -04:00
chyyran 828464c351 rt(image): helper to load image from shaderpack TextureBuffer 2024-10-01 23:25:52 -04:00
chyyran c19593e289 pack: implement shader pack library to preload resources from disk 2024-10-01 23:25:52 -04:00
chyyran 1bbbd784d8 preset: make ShaderPassConfig and TextureConfig type aliases to PathReference 2024-10-01 23:25:52 -04:00
chyyran 4273a833e8 pack: add librashader-pack
also consolidate rayon dependency
2024-10-01 23:25:52 -04:00
chyyran f14f45b3b1 rt: separate out meta information for textures 2024-10-01 23:25:52 -04:00
chyyran 859d16e64e rt: separate out meta information to its own struct 2024-10-01 23:25:52 -04:00
chyyran b395b94a40 doc(abi2): add migration details for libra_source_image_d3d12_t layout 2024-09-30 18:01:57 -04:00
Ronny Chan 90a9ee754d
doc: fix migration api typo 2024-09-30 11:38:13 -04:00
chyyran 34b50059ca doc(d3d12,vk): clear up language for barriers
also remove useless `libra_PFN_vkGetInstanceProcAddr`
2024-09-30 11:35:38 -04:00
chyyran 7a13136f9a capi(d3d12): allow d3d12 to optionally use a resource handle with chain-managed descriptors 2024-09-30 11:35:38 -04:00
chyyran 0cb3880d7f rt(d3d12): allow construction of D3D12InputImage without a descriptor handle 2024-09-30 11:35:38 -04:00
chyyran c526b7043a capi(d3d12): change the layout so that descriptor is first in libra_source_image_d3d12_t 2024-09-30 11:35:38 -04:00
chyyran 33d95ac399 rt(d3d12): allow creating input view without a CPU handle 2024-09-30 11:35:38 -04:00
chyyran 40691cc406 rt(d3d12): allow creating output view from a resource ref 2024-09-30 11:35:38 -04:00
chyyran 3c20c83bc9 rt(d3d12): use InterfaceRef for D3D12InputImage to avoid refcount for input image 2024-09-30 11:35:38 -04:00
chyyran b123f63a6e doc(reflect): fix link 2024-09-29 01:41:41 -04:00
chyyran 4285ad2bd1 rt(d3d12): allow a pipeline to be available for multiple formats without recompilation 2024-09-29 01:04:11 -04:00
chyyran c57e502b78 rt(d3d12): make images with OwnedFramebuffer provenance use ManuallyDrop 2024-09-29 01:04:11 -04:00
chyyran 7edff0ae35 rt(d3d12): allow manually specifying the strategy to get a manuallydrop for luts 2024-09-29 01:04:11 -04:00
chyyran dedde05c83 rt(d3d12): move update subresources to lut 2024-09-29 01:04:11 -04:00
chyyran 7d483f2e08 rt(d3d9): take viewport by reference to avoid AddRef/Release 2024-09-28 14:46:16 -04:00
chyyran 7b7fd99b92 rt(d3d11): take viewport by reference to avoid AddRef/Release 2024-09-28 14:46:16 -04:00
chyyran 28931ae50a rt(d3d12): update d3d12-descriptor-heap 2024-09-28 14:46:16 -04:00
chyyran 2fe7702957 rt(d3d11): avoid QueryInterface in GetSize 2024-09-28 14:46:16 -04:00
chyyran a5c8fcf4f8 doc: missing period 2024-09-27 01:13:37 -04:00
chyyran 629070ea2f ci: build cli 2024-09-27 01:12:02 -04:00
chyyran da53c3df59 cli(render): make frame inclusive to ensure correct feedback and history behaviour 2024-09-27 01:12:02 -04:00
chyyran 84e78f4e48 cli(render): add ability to specify frame options 2024-09-27 01:12:02 -04:00
chyyran 91f8089277 cli(render): add ability to specify params and passes enabled 2024-09-27 01:12:02 -04:00
chyyran 3993f57271 doc(cli): document CLI 2024-09-27 01:12:02 -04:00
chyyran 55ff7a93f2 cli: rename librashader-test -> librashader-cli 2024-09-27 01:12:02 -04:00
chyyran 227757a221 cli: parse transpile versions 2024-09-27 01:12:02 -04:00
chyyran a72a593029 reflect: allow indexed and non-indexed semantics to be serialized and deserialized as strings 2024-09-27 01:12:02 -04:00
chyyran 1537c1bcd7 cli: ensure shaders are validated before compile 2024-09-27 01:12:02 -04:00
chyyran 5573f13227 test: add CLI with multiple functions 2024-09-27 01:12:02 -04:00
chyyran bac09ad2a3 reflect: allow validation of shaders without reflecting against semantics 2024-09-27 01:12:02 -04:00
chyyran 31ece05246 reflect: allow more reflection objects to be serializable 2024-09-27 01:12:02 -04:00
chyyran 5ede061975 preprocess: allow shadersource to be serializable with serde 2024-09-27 01:12:02 -04:00
chyyran 1676150858 presets: allow presets to be serializable with serde 2024-09-27 01:12:02 -04:00
chyyran eeda0d02d0 test: make RenderTest trait object safe 2024-09-27 01:12:02 -04:00
chyyran f7a938a00d deps: update dependencies 2024-09-25 22:26:20 -04:00
chyyran 6394b28d40 deps: update dependencies 2024-09-25 22:14:46 -04:00
chyyran 443fa20d22 test: clean up imports and features 2024-09-25 22:00:10 -04:00
chyyran 5c726efe21 test(d3d12): Add D3D12 render test 2024-09-25 22:00:10 -04:00
chyyran d33c2a84b2 test(d3d9): Add Direct3D 9 render test
Something seems to be broken though, it's not rendering the correct channel
2024-09-25 22:00:10 -04:00
chyyran 5a35a2bd1e test(render): reenable cache 2024-09-25 22:00:10 -04:00
chyyran 41034330a7 test(mtl): Add Metal render test 2024-09-25 22:00:10 -04:00
chyyran a7836923d7 test: split test harnesses into features 2024-09-25 22:00:10 -04:00
chyyran 20039b9347 test: abstract test framework 2024-09-25 22:00:10 -04:00
chyyran 799d409ddb test(gl): Add OpenGL render test 2024-09-25 22:00:10 -04:00
chyyran 79513a301e test(vk): Add Vulkan render test 2024-09-25 22:00:10 -04:00
chyyran 6de2de8d12 test(wgpu): Add WGPU render test
Also rename triangle -> render
2024-09-25 22:00:10 -04:00
chyyran 2904a2ac10 test(d3d11): Add D3D11 triangle test to image 2024-09-25 22:00:10 -04:00
chyyran 341fbceb82 rt(d3d12): derive clone on D3D12InputImage 2024-09-25 22:00:10 -04:00
chyyran c54747d398 deps: update dependencies 2024-09-25 18:16:21 -04:00
chyyran 3888b56c83 rt(d3d9): accept input image by reference 2024-09-25 01:44:36 -04:00
chyyran b0df631651 rt(d3d9): load LUTS as BGRA8
Seems like ARGB8 is incorrect.
2024-09-25 01:44:36 -04:00
chyyran dbfa822f7c rt(d3d9): disable alpha blending 2024-09-25 01:44:36 -04:00
chyyran e7fe96520e rt(gl): ensure framebuffers are bound 2024-09-25 01:44:36 -04:00
chyyran 97ad0d64bf rt(gl): make FilterChain::load_from argument order consistent with other runtimes 2024-09-24 00:49:10 -04:00
chyyran feaebc5f44 fmt(gl): cargo fmt 2024-09-23 23:55:50 -04:00
chyyran ef35e2a620 presets: rename to_hashmap -> into_hashmap 2024-09-23 23:55:27 -04:00
chyyran 987e967269 doc(capi): add error doc for infallible error 2024-09-22 02:06:47 -04:00
chyyran c3469520f9 examples(d3d11): fix d3d11 examples 2024-09-22 02:06:47 -04:00
chyyran 45b98a2bdd rt(gl): use array_init instead of manually doing it with MaybeUninit 2024-09-22 02:06:47 -04:00
chyyran 2ee9eca854 ci: ignore .DS_Store 2024-09-22 02:06:47 -04:00
chyyran 59937aced5 fmt: cargo fmt 2024-09-22 02:06:47 -04:00
chyyran e4eed34c10 capi: const qualify chain in _filter_chain_get_param and _filter_chain_get_active_pass_count"
This reverts commit 98d8d91c66.
2024-09-22 02:06:47 -04:00
chyyran 7abd679bd7 doc: add migration docs 2024-09-22 02:06:47 -04:00
chyyran 41353ac9c4 rt(gl): remove need for explicit external FBO object
Replaced with an internal FBO that is state tracked so as to not recreate it every frame, but will update if necessary
2024-09-22 02:06:47 -04:00
chyyran 4d790e7a7b doc: update ABI versions in version.rs and readme 2024-09-22 02:06:47 -04:00
chyyran aeb0a16cfb rt(vk): allow specifying the queue to use 2024-09-22 02:06:47 -04:00
chyyran 16108838b1 rt(d3d11): remove unneeded transmutes 2024-09-22 02:06:47 -04:00
chyyran 7cfbca7755 rt(wgpu): remove unnecessary output move 2024-09-22 02:06:47 -04:00
chyyran 8467e5cd97 rt(mtl): remove unnecessary output move 2024-09-22 02:06:47 -04:00
chyyran 1e33b4cc03 capi: make viewport optional, defaulting to a viewport that is the entire size of the render target 2024-09-22 02:06:47 -04:00
chyyran eb53699590 capi(include): update headers 2024-09-22 02:06:47 -04:00
chyyran 0cbf8024a7 build: update soname 2024-09-22 02:06:47 -04:00
chyyran 7e354a56a1 capi: upgrade cbindgen to fix syn bug 2024-09-22 02:06:47 -04:00
chyyran 0a87bcc657 rt: respect viewport extents when rendering 2024-09-22 02:06:47 -04:00
chyyran f0a7970b44 rt: add viewport extent separate from output texture dimensions 2024-09-22 02:06:47 -04:00
chyyran e7e6ed8fb8 capi: remove _internal_alloc from libra_preset_param_list_t 2024-09-22 02:06:47 -04:00
chyyran 61fdaeea14 capi(gl): update to take opengl context in constructor 2024-09-22 02:06:47 -04:00
chyyran 2552d4321b capi(gl): set output in libra_output_image_gl_t, not viewport 2024-09-22 02:06:47 -04:00
chyyran eaf939c861 capi(vk): set output in libra_output_image_vk_t, not viewport 2024-09-22 02:06:47 -04:00
chyyran 14abb0362b capi(d3d12): set output in libra_output_image_d3d12_t, not viewport 2024-09-22 02:06:47 -04:00
chyyran e064f8d0be capi(d3d12): remove ignored fields from libra_source_image_d3d12_t 2024-09-22 02:06:47 -04:00
chyyran f18c22a95a capi(d3d11): take ManuallyDrop<ID3D11ShaderResourceView> directly in libra_d3d11_filter_chain_t 2024-09-22 02:06:47 -04:00
chyyran 763c05755d rt: mark error types as non_exhaustive 2024-09-22 02:06:47 -04:00
chyyran e6d23f1d8f doc: update opengl example 2024-09-22 02:06:47 -04:00
chyyran 2f9df143cf deps: consolidate and update dependencies 2024-09-22 02:06:47 -04:00
chyyran 1bdadaa449 rt(gl): port to OpenGL runtime to glow 2024-09-22 02:06:47 -04:00
chyyran 77b957bf5e reflect: stop adding null bytes to uniform names 2024-09-22 02:06:47 -04:00
chyyran 1e0727f89d chore: Release 2024-09-21 01:55:42 -04:00
chyyran 60d2c3f177 chore: Release 2024-09-21 01:53:56 -04:00
chyyran 72a98272f3 doc(capi): document all C API members 2024-09-21 01:50:05 -04:00
chyyran 7d0b135710 rt(mtl): remove unused 'pass lifetime 2024-09-20 02:21:09 -04:00
chyyran 4ba5aefafc capi: don't export wrap_ok macro
This was done by accident, so will be made exempt from Rust versioning guarantees.
2024-09-18 00:55:05 -04:00
chyyran 6edfaed91f test: update slang-shaders 2024-09-18 00:27:52 -04:00
chyyran cd877d7883 build: respect CARGO env in build script 2024-09-17 21:25:10 -04:00
chyyran d56d92ea0f test: update slang-shaders 2024-09-17 19:07:51 -04:00
chyyran f49e3b58d8 doc(capi): fix rotation integer docs 2024-09-17 19:06:57 -04:00
jcm 1f4f1b6c12 capi: Fix Metal error message, function header 2024-09-17 18:27:43 -04:00
chyyran 57f2dabf0c reflect: fix CurrentSubFrame being parsed as TotalSubFrames 2024-09-17 18:27:31 -04:00
chyyran 81840a9e9c reflect: insert alias semantics for shader name through pragma 2024-09-17 18:27:31 -04:00
chyyran 87e0405675 presets: properly trim shader name if given through pragma 2024-09-17 18:27:31 -04:00
chyyran e0c97f77b4 build: nicer build script messages 2024-09-17 01:30:19 -04:00
chyyran 6e60dd6fa0 test: update slang-shaders 2024-09-17 01:29:08 -04:00
chyyran d55d0e4839 rt(mtl): use f32::MAX for lodMaxClamp 2024-09-17 01:20:48 -04:00
chyyran 6cdd9247de presets: trim aliases and shader parameter names 2024-09-16 19:21:18 -04:00
chyyran cbe6510f76 preprocess: the step argument is optional according to slang-shaders spec 2024-09-16 19:21:18 -04:00
chyyran d6f8950bdc rt: only draw last pass twice if the last pass is needed as feedback 2024-09-15 15:46:31 -04:00
chyyran 0fe5bbd57b rt: do reflection to see if the final pass is needed as feedback 2024-09-15 15:46:31 -04:00
chyyran eace595ebb chore: Release 2024-09-15 11:00:38 -04:00
chyyran 66561ad2ed doc: fix header image 2024-09-15 03:11:45 -04:00
chyyran 05d48841ad doc: update docs for stable build instructions 2024-09-15 03:10:45 -04:00
chyyran 927740433c build: remove unneeded image dependency 2024-09-15 03:10:45 -04:00
chyyran 316e92dc09 build: allow building stable with --stable flag 2024-09-15 03:10:45 -04:00
chyyran e930f90a9c capi: allow building on stable by gating #[doc] comments 2024-09-15 03:10:45 -04:00
chyyran 2f988d5b1d rt: add stable feature to build ShaderPassArtifacts via Box<dyn CompileReflectShader> 2024-09-15 03:10:45 -04:00
chyyran e0a5c90103 reflect: implement stable FromCompilation 2024-09-15 03:10:45 -04:00
chyyran 4cc3c875bf reflect: allow compilation of boxed trait objects
Add a hidden `compile_boxed` function to CompileShader to support this. This is to allow Box<dyn CompileReflectShader> to work.
2024-09-15 03:10:45 -04:00
chyyran 805854b94b reflect: simplify FromCompilation output signature
CompileShader<..> + ReflectShader and be simplified to CompileReflectShader since FromCompilation instances are unique for (Compilation, Reflector)
2024-09-15 03:10:45 -04:00
chyyran c291d9d85f capi: remove dependency on try_blocks
Uses an IIFE + return type changes to remove the need for try blocks
2024-09-14 21:26:19 -04:00
chyyran 8b2ff57ee8 reflect: replace let_chains with Option::filter 2024-09-14 02:11:18 -04:00
chyyran 3d9139b4e0 rt(d3d11): replace let_chains with stable alternatives 2024-09-14 02:11:18 -04:00
chyyran b432a1e02d rt(d3d12): replace let_chains with stable alternatives 2024-09-14 02:11:18 -04:00
chyyran ab9ab6fe68 rt(gl): use Option::filter instead of let_chains 2024-09-14 02:11:18 -04:00
chyyran 0ba4c482b3 rt(vk): use Option::filter instead of let_chains 2024-09-14 02:11:18 -04:00
chyyran 57f9a13ee7 rt(wgpu): use Option::filter instead of let_chains 2024-09-14 02:11:18 -04:00
chyyran 2661effab4 chore: Release 2024-09-13 17:31:17 -04:00
chyyran 02288554b9 rt(mtl): fix history buffer off-by-one by buffering the history read. 2024-09-13 17:21:03 -04:00
chyyran 98d8d91c66 capi: undo const qualification of chain in _filter_chain_get_param and _filter_chain_get_active_pass_count
This should be done over an ABI bump, even if the headers weren't updated.
2024-09-13 17:21:03 -04:00
chyyran 6f5b342c1b build: fix dev-dependencies on non-apple 2024-09-13 17:21:03 -04:00
chyyran bec0482513 ci: trigger ci 2024-09-13 01:18:11 -04:00
chyyran e24beede0b rt(mtl): draw final pass to output targets 2024-09-13 00:59:12 -04:00
chyyran 22b2118e97 rt(mtl): don't resize if the requested format differs from optimal format 2024-09-13 00:59:12 -04:00
chyyran 856f69113d rt(mtl): don't make mipmapper for history if not needed 2024-09-13 00:59:12 -04:00
chyyran 477d0ae67c rt(wgpu): draw final pass to output targets 2024-09-13 00:59:12 -04:00
chyyran e68da7b984 rt(gl): draw final pass to output targets 2024-09-13 00:59:12 -04:00
chyyran 666588ef0d rt(vk): draw final pass to output targets 2024-09-13 00:59:12 -04:00
chyyran 336f540ce9 rt(d3d9): draw final pass to output targets 2024-09-13 00:59:12 -04:00
chyyran 894d19eb81 rt(d3d12): draw final pass to output targets 2024-09-13 00:59:12 -04:00
chyyran 1bf5d7efca rt(d3d11): draw final pass to output targets 2024-09-13 00:59:12 -04:00
chyyran c9205bc922 rt(mtl): remove internal_frame_count 2024-09-12 00:30:23 -04:00
chyyran cebc7a939a rt(mtl): fix faulty history but only copying one mipmap slice of texture
rt(mtl): push history before doing writing

This does make the metal backend one frame behind compared to the other stuff but eh
2024-09-12 00:30:23 -04:00
chyyran fe48fd03a5 test(mtl): update the metal test 2024-09-12 00:30:23 -04:00
chyyran 50580cfc3b chore: Release 2024-09-08 11:23:08 -04:00
chyyran 075a2981e1 rt(gl): fix glShaderSource going past the buffer 2024-09-08 11:20:38 -04:00
chyyran a7dd40a79f ci: allow c_str_literals to maintain a lower MSRV 2024-09-06 23:42:18 -04:00
chyyran fd8d6f6ab8 chore: Release 2024-09-06 23:01:07 -04:00
chyyran c5fdffa6aa dep: update cargo.lock 2024-09-06 22:45:07 -04:00
chyyran 090e268c4a doc: improve docs 2024-09-06 22:45:07 -04:00
chyyran 8856a78eb8 api: expose FastHashMap and ShortString 2024-09-06 22:45:07 -04:00
chyyran ec3add1616 rt(params): rename get_parameter -> parameter_value 2024-09-06 22:45:07 -04:00
chyyran f83fd1e98f ci: debug is broken on windows 7 2024-09-06 22:45:07 -04:00
chyyran c3033cfbbf reflect: port to spirv-cross2 2024-09-06 22:45:07 -04:00
chyyran 820fb69328 reflect: remove get_prefix from get_texture_semantic/get_unique_semantic 2024-09-06 22:45:07 -04:00
chyyran c9a6411394 deps: consolidate and update dependencies 2024-09-06 22:45:07 -04:00
chyyran 40a56bf165 reflect/preprocess/presets: use SmallString where it makes sense
Most use cases are internal so at least for the runtime consumers there's no API change, but preset parsing
and reflection items are public API so might as well put this as part of 0.4.0
2024-09-06 22:45:07 -04:00
chyyran c447e40583 rt: redesign parameters to be thread-safe across FFI using arcswap and atomicusize
This should allow C FFI consumers to modify frame parameters from a different thread without it being UB.
2024-09-06 22:45:07 -04:00
chyyran ae76bf9cc1 rt(d3d12): pull out descriptor heap implementation into its own crate 2024-09-06 22:45:07 -04:00
chyyran e934f175ef rt(d3d12): stop leaking transition barriers 2024-08-25 23:59:29 -04:00
chyyran 2e7c3b3273 rt(d3d12): use gpu_allocator instead of CreateCommittedResource 2024-08-25 01:26:25 -04:00
chyyran e90c27ebbd rt(vk): use manuallydrop for VulkanBuffer rather than Option 2024-08-25 01:26:25 -04:00
chyyran 8fb5d48a1e chore: Release 2024-08-21 20:13:23 -04:00
chyyran b7fd3bc507 runtime: fix image swizzle for remainder 2024-08-21 20:12:10 -04:00
chyyran af05cc5bd8 ci: build for windows 7 2024-08-21 01:40:29 -04:00
chyyran 2f0a3356d9 rel(librashader@0.3.2): doc fix 2024-08-21 01:05:24 -04:00
chyyran 0efaf2b8a0 doc: fix docsrs build on macos 2024-08-21 01:03:44 -04:00
chyyran 436d1fe3cd chore: Release 2024-08-21 00:38:32 -04:00
chyyran 0ce11bac71 doc: promote d3d9 to secondary support
Fixing `global` allows feedback and history, so a lot of things will work.
2024-08-20 23:13:39 -04:00
chyyran bc269c270c rt(d3d9): remove backtrace from Direct3DError 2024-08-20 23:13:39 -04:00
chyyran db3d5d05a6 presets: use crate for extract_if with fully qualified syntax to avoid name collision with unstable std 2024-08-20 23:13:39 -04:00
chyyran 1957f576ca reflect: further improve link i/o pass by ensuring that the output SPIR-V is valid according to Vulkan 1.0 semantics
The pass will now create and reassign types of downgraded outputs to a type with Private storage class, without affecting the type of other outputs.

Downgraded outputs also get removed from the entrypoint interface
2024-08-20 23:13:39 -04:00
chyyran 5560c1ed09 runtime: use a more efficient swizzling implementation without array_chunks_mut 2024-08-20 23:13:39 -04:00
chyyran f8c055524b doc(readme): document shader pre-linkage 2024-08-20 00:10:57 -04:00
chyyran fee92113f1 ci: remove PR full test
Very rare we want to run the full test suite anyways on PRs.
2024-08-20 00:10:57 -04:00
chyyran 45c3c876f4 reflect: remove explicit compile function in favour of TryFrom 2024-08-19 18:33:30 -04:00
chyyran 28f5674a80 presets: remove rustc 1.74 compatibility
MSRV is 1.76
2024-08-19 18:33:30 -04:00
chyyran bffad12ea2 doc(presets): add docstring for ContextItem::key 2024-08-19 18:33:30 -04:00
chyyran 321e30a0f1 capi: extend config_struct macro to handle overrides 2024-08-19 18:33:30 -04:00
chyyran 9b40c10466 reflect: improve inout link algorithm 2024-08-19 18:19:21 -04:00
Ronny Chan cd14bca23a Revert "reflect: improve inout link algorithm"
This reverts commit 1ac78695c6.
2024-08-19 01:53:38 -04:00
chyyran 1ac78695c6 reflect: improve inout link algorithm 2024-08-19 01:40:22 -04:00
228 changed files with 14138 additions and 8064 deletions

View file

@ -38,10 +38,24 @@ jobs:
- name: Build dynamic library
run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }}
- name: Upload build artifacts
uses: actions/upload-artifact@v3.1.2
uses: actions/upload-artifact@v4.4.0
with:
name: ${{ format('librashader-{0}-{1}-{2}', matrix.output, github.sha, matrix.profile) }}
path: ${{ format('target/{0}/librashader.*', matrix.profile) }}
- name: Install Ubuntu librashader CLI build dependencies
if: matrix.profile == 'release' && matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update || true
sudo apt-get -y install xorg-dev
- name: Build librashader CLI
if: matrix.profile == 'release'
run: cargo build -p librashader-cli --release
- name: Upload librashader-cli
uses: actions/upload-artifact@v4.4.0
if: matrix.profile == 'release'
with:
name: ${{ format('librashader-cli-{0}-{1}', matrix.output, github.sha) }}
path: ${{ format('target/{0}/librashader-cli*', matrix.profile) }}
build-ubuntu-arm64:
strategy:
matrix:
@ -65,14 +79,23 @@ jobs:
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted" | sudo tee -a /etc/apt/sources.list
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted" | sudo tee -a /etc/apt/sources.list
sudo apt-get update || true
sudo apt-get -y install g++-aarch64-linux-gnu gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
sudo apt-get -y install g++-aarch64-linux-gnu gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu xorg-dev libx11-dev:arm64 libxrandr-dev:arm64
- name: Build dynamic library
run: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} --target aarch64-unknown-linux-gnu
- name: Upload build artifacts
uses: actions/upload-artifact@v3.1.2
uses: actions/upload-artifact@v4.4.0
with:
name: ${{ format('librashader-aarch64-ubuntu-{0}-{1}', github.sha, matrix.profile) }}
path: ${{ format('target/aarch64-unknown-linux-gnu/{0}/librashader.*', matrix.profile) }}
- name: Build librashader CLI
if: matrix.profile == 'release'
run: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc cargo build -p librashader-cli --release --target aarch64-unknown-linux-gnu
- name: Upload librashader-cli
uses: actions/upload-artifact@v4.4.0
if: matrix.profile == 'release'
with:
name: ${{ format('librashader-cli-aarch64-ubuntu-{0}', github.sha) }}
path: ${{ format('target/aarch64-unknown-linux-gnu/{0}/librashader-cli', matrix.profile) }}
build-windows-arm64:
strategy:
matrix:
@ -86,13 +109,43 @@ jobs:
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2024-01-15 # pinned because it seems like there's a segfault on nightly
override: true
toolchain: nightly
targets: aarch64-pc-windows-msvc
- name: Build dynamic library
run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} --target aarch64-pc-windows-msvc
- name: Upload build artifacts
uses: actions/upload-artifact@v3.1.2
uses: actions/upload-artifact@v4.4.0
with:
name: ${{ format('librashader-aarch64-windows-{0}-{1}', github.sha, matrix.profile) }}
path: ${{ format('target/aarch64-pc-windows-msvc/{0}/librashader.*', matrix.profile) }}
- name: Build librashader CLI
if: matrix.profile == 'release'
run: cargo build -p librashader-cli --release --target aarch64-pc-windows-msvc
- name: Upload librashader-cli
uses: actions/upload-artifact@v4.4.0
if: matrix.profile == 'release'
with:
name: ${{ format('librashader-cli-aarch64-pc-windows-msvc-{0}', github.sha) }}
path: ${{ format('target/aarch64-pc-windows-msvc/{0}/librashader-cli.exe', matrix.profile) }}
build-windows-7:
strategy:
matrix:
profile: ['release', 'optimized']
fail-fast: false
runs-on: windows-latest
name: x86_64-win7-windows
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
components: rust-src
- name: Build dynamic library
run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} --target x86_64-win7-windows-msvc -- -Zbuild-std
- name: Upload build artifacts
uses: actions/upload-artifact@v4.4.0
with:
name: ${{ format('librashader-x86_64-win7-windows-{0}-{1}', github.sha, matrix.profile) }}
path: ${{ format('target/x86_64-win7-windows-msvc/{0}/librashader.*', matrix.profile) }}

View file

@ -1,61 +0,0 @@
name: build Linux packages with Open Build Service
on:
pull_request_target:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
jobs:
approve-obs-build:
name: "approval"
runs-on: ubuntu-latest
steps:
- name: Approve
run: echo OBS build CI test runs need to be approved by a maintainer.
build-obs-binary:
environment:
name: obs-build-env
strategy:
matrix:
include:
- repo: Fedora_40
spec: librashader.spec
can_fail: true
name: Fedora 40 (.rpm)
- repo: xUbuntu_24.04
spec: librashader.spec
can_fail: true
name: Ubuntu 24.04 (.deb)
runs-on: ubuntu-latest
needs: [approve-obs-build]
continue-on-error: ${{ matrix.can_fail }}
name: ${{ matrix.name }}
container:
image: fedora:39
options: --privileged
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install OSC and dependencies
env:
OBS_CONFIG: ${{ secrets.OBS_CONFIG }}
run: |
sudo dnf install -y osc obs-service-obs_scm obs-service-cargo_vendor cargo python3-zstandard
mkdir -p ~/.config/osc
echo "$OBS_CONFIG" > ~/.config/osc/oscrc
- name: Checkout Open Build Service repository
run: |
osc co home:chyyran:librashader/librashader
- name: Copy spec from repository
run: |
cp -r ./pkg/obs/ home:chyyran:librashader/librashader/
sed -r -i 's/(<param name="revision">)(.+)(<\/param>)/<param name="revision">${{ github.sha }}<\/param>/g' home:chyyran:librashader/librashader/_service
- name: Vendor sources for Open Build Service
run: |
cd home:chyyran:librashader/librashader/
osc service mr
- name: Build package
run: |
cd home:chyyran:librashader/librashader/
osc build --no-verify --trust-all-projects ${{ matrix.repo }} x86_64 ${{ matrix.spec }}

View file

@ -1,83 +0,0 @@
name: integration test shader reflection
on:
pull_request:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
jobs:
approve-full-test:
name: "approval"
runs-on: ubuntu-latest
steps:
- name: Approve
run: echo Full test suite for PRs needs approval by a maintainer
test-presets:
runs-on: ubuntu-latest
continue-on-error: false
environment:
name: full-test
needs: [approve-full-test]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
- name: Test preset processing
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture preprocess_all_slang_presets_parsed
test-naga:
runs-on: ubuntu-latest
continue-on-error: false
environment:
name: full-test
needs: [ approve-full-test ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
- name: Test Naga reflection
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture compile_all_slang_presets_wgsl_naga compile_all_slang_presets_msl_naga compile_all_slang_presets_spirv_naga
test-cross:
runs-on: ubuntu-latest
continue-on-error: false
environment:
name: full-test
needs: [ approve-full-test ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
- name: Test SPIRV-Cross
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture compile_all_slang_presets_msl_cross compile_all_slang_presets_glsl_cross compile_all_slang_presets_hlsl_cross compile_all_slang_presets_spirv_cross
test-dxil:
runs-on: windows-latest
continue-on-error: false
environment:
name: full-test
needs: [ approve-full-test ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
- name: Test DXIL
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture compile_all_slang_presets_dxil_cross

View file

@ -13,7 +13,7 @@ jobs:
container: fedora:39
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install OSC and dependencies
env:
OBS_CONFIG: ${{ secrets.OBS_CONFIG }}

1
.gitignore vendored
View file

@ -13,3 +13,4 @@ librashader_runtime_*.exe
/test/capi-tests/librashader-capi-tests/**/*.so
/test/capi-tests/librashader-capi-tests/**/*.out
/test/Mega_Bezel_Packs/Duimon-Mega-Bezel/
.DS_Store

View file

@ -3,38 +3,13 @@
The following shaders are known to be broken due to various issues.
This list is updated as of [slang-shaders@`356678e`](https://github.com/libretro/slang-shaders/commit/356678ec53ca940a53fa509eff0b65bb63a403bb)
This list is updated as of [slang-shaders@`33876b3`](https://github.com/libretro/slang-shaders/commit/33876b3578baac8302b6189ac7acbb052013919e)
## Broken due to parsing errors
librashader's preset parser is somewhat stricter than RetroArch in what it accepts. All shaders and textures in a preset must
resolve to a fully canonical path to properly parse. The following shaders have broken paths.
* `bezel/Mega_Bezel/shaders/hyllian/crt-super-xbr/crt-super-xbr.slangp`: Missing `bezel/Mega_Bezel/shaders/hyllian/crt-super-xbr/shaders/linearize.slang`
* `crt/crt-maximus-royale-fast-mode.slangp`: Missing `crt/shaders/crt-maximus-royale/FrameTextures/16_9/TV_decor_1.png`
* `crt/crt-maximus-royale-half-res-mode.slangp`: Missing `crt/shaders/crt-maximus-royale/FrameTextures/16_9/TV_decor_1.png`
* `crt/crt-maximus-royale.slangp`: Missing `crt/shaders/crt-maximus-royale/FrameTextures/16_9/TV_decor_1.png`
* `crt/mame_hlsl.slangp`: Missing `crt/shaders/mame_hlsl/shaders/lut.slang`
* `denoisers/fast-bilateral-super-2xbr-3d-3p.slangp`: Missing `xbr/shaders/super-xbr/super-2xbr-3d-pass0.slang`
* `presets/tvout/tvout+ntsc-256px-composite.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
* `presets/tvout/tvout+ntsc-256px-svideo.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-3phase.slang`
* `presets/tvout/tvout+ntsc-2phase-composite.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-2phase.slang`
* `presets/tvout/tvout+ntsc-2phase-svideo.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-2phase.slang`
* `presets/tvout/tvout+ntsc-320px-composite.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-2phase.slang`
* `presets/tvout/tvout+ntsc-320px-svideo.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-2phase.slang`
* `presets/tvout/tvout+ntsc-3phase-composite.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
* `presets/tvout/tvout+ntsc-3phase-svideo.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-3phase.slang`
* `presets/tvout/tvout+ntsc-nes.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-256px-composite+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-256px-svideo+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-3phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-2phase-composite+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-2phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-2phase-svideo+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-2phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-320px-composite+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-2phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-320px-svideo+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-2phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-3phase-composite+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-3phase-svideo+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-3phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-nes+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
* `scalefx/shaders/old/scalefx-9x.slangp`: Missing `../stock.slang`
* `scalefx/shaders/old/scalefx.slangp`: Missing `../stock.slang`
* No known broken presets.
librashader's parser is fuzzed with slang-shaders and will accept invalid keys like `mipmap1` or `filter_texture = linear`
to account for shader presets that use these invalid constructs. No known shader presets fail to parse due to syntax errors
@ -44,5 +19,4 @@ that haven't already been accounted for.
The preprocessor resolves `#include` pragmas in each `.slang` shader and recursively flattens files into a single compute unit.
* `misc/shaders/glass.slang`: Missing `misc/include/img/param_floats.h`.
* Looks like this was moved too deep, it references `../include/img/param_floats.h`, but the shader lives in the `misc/shaders/` folder.
* `bezel/Mega_Bezel/shaders/hyllian/crt-super-xbr/crt-super-xbr.slangp`: GLSL error: `ERROR: common-functions-bezel.inc:63: 'HSM_GetInverseScaledCoord' : no matching overloaded function found `

1007
CLI.md Normal file

File diff suppressed because it is too large Load diff

1367
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,17 +8,41 @@ members = [
"librashader-runtime",
"librashader-runtime-d3d11",
"librashader-runtime-d3d12",
"librashader-runtime-d3d9",
"librashader-runtime-gl",
"librashader-runtime-vk",
"librashader-runtime-mtl",
"librashader-runtime-wgpu",
"librashader-cache",
"librashader-capi",
"librashader-build-script", "librashader-runtime-d3d9"]
"librashader-build-script",
"librashader-cli", "librashader-pack"]
resolver = "2"
[workspace.dependencies]
windows = "0.58.0"
ash = "0.38"
spirv-cross2 = { version = "0.4", default-features = false }
objc2-metal = { version = "0.2" }
objc2 = { version = "0.5.0" }
glow = { version = "0.14.1" }
glfw = { version = "0.58.0"}
wgpu = { version = "22", default-features = false }
wgpu-types = { version = "22" }
clap = { version = "=4.3.0", features = ["derive"] }
rayon = { version = "1.10.0"}
[workspace.dependencies.image]
version = "0.25.2"
features = [
"gif", "jpeg", "png",
"tga", "pnm", "tiff",
"webp", "bmp", "dds",
]
default-features = false
[workspace.metadata.release]

120
MIGRATION-ABI2.md Normal file
View file

@ -0,0 +1,120 @@
# Migrating to librashader ABI 2
librashader version 0.5.0 introduces an ABI change to the C ABI that is incompatible with prior versions.
If a version of librashader prior to 0.5.0 was linked via `libashader_ld.h`, it should _correctly_ fail to load when attempting
to load an instance of librashader 0.5.0 or later.
This document is only relevant to consumers of the librashader C ABI. Rust users should consult [docs.rs/librashader](https://docs.rs/librashader/latest/librashader/).
## `LIBRASHADER_CURRENT_ABI` change
`LIBRASHADER_CURRENT_ABI` has changed from `1` to `2`. There is no change to `LIBRASHADER_CURRENT_API`, as new features
have not been added. Option structs should continue to pass `LIBRASHADER_CURRENT_API` for the latest available feature set.
## `SONAME` change
On Linux, the canonical `SONAME` of `librashader.so` has changed from `librashader.so.1` to `librashader.so.2`. This will
affect consumers that link via the linkage table with `-lrashader` or equivalent rather than through `libashader_ld.h`.
## `libra_preset` changes
* The `_internal_alloc` field from `libra_preset_param_list_t` has been removed. This change was made to reduce the potential
surface area for undefined behaviour to occur. If librashader was used correctly, there is no code change required.
## `LIBRA_RUNTIME_VULKAN` changes
The following changes are applicable if `LIBRA_RUNTIME_VULKAN` is defined.
* The `libra_output_image_vk_t` and `libra_source_image_vk_t` structs have been replaced with `libra_image_vk_t`.
* `libra_image_vk_t`has the same layout and semantics for `libra_source_image_vk_t`.
* When passed as `out`, you should now pass what was previously `.width` and `.height` of `libra_viewport_t` to the same fields in `libra_image_vk_t`. The `handle` and `format`
fields retain the same semantics as `libra_output_image_vk_t`.
* A field `queue` of type `VkQueue` was added to `libra_device_vk_t`. This field can be `NULL`. If not `NULL`, it is the handle to the `VkQueue` graphics queue
to use for the filter chain. If `NULL`, a suitable queue will be chosen.
* The `image` and `out` parameters of `libra_vk_filter_chain_frame` has changed from `libra_source_image_vk_t` and `libra_output_image_vk_t`, to `libra_image_vk_t`.
* In `libra_vk_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
* The `chain` parameter of `libra_vk_filter_chain_get_param` has been made `const`.
* It is always thread safe to call `libra_vk_filter_chain_set_param` from any thread [^1].
## `LIBRA_RUNTIME_OPENGL` changes
The following changes are applicable if `LIBRA_RUNTIME_OPENGL` is defined.
* The `libra_gl_init_context` function has been removed.
* The function `libra_gl_filter_chain_create` now accepts a `loader` parameter of type `libra_gl_loader_t`. This will be the OpenGL loader used to create the filter chain, previously passed to `libra_gl_init_context`
The filter chain will be created against the current OpenGL context.
* The `libra_output_framebuffer_gl_t` and `libra_source_image_gl_t` structs have been replaced with `libra_image_gl_t`.
* `libra_image_gl_t`has the same layout and semantics for `libra_source_image_gl_t`.
* When passed as `out`, you should now pass what was previously `.width` and `.height` of `libra_viewport_t` to the same fields in `libra_image_gl_t`. The `handle` and `format`
fields retain the same semantics as `libra_output_image_gl_t`.
* The `fbo` field previously in `libra_output_image_gl_t` is no longer necessary. librashader will now internally manage a framebuffer object to write to the provided texture.
* In `libra_gl_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
* The `chain` parameter of `libra_gl_filter_chain_get_param` has been made `const`.
* It is always thread safe to call `libra_gl_filter_chain_set_param` from any thread [^1].
## `LIBRA_RUNTIME_D3D11` changes
The following changes are applicable if `LIBRA_RUNTIME_D3D11` is defined.
* The `image` parameter of `libra_d3d11_filter_chain_frame` has changed from `libra_source_image_d3d11_t` to `ID3D11ShaderResourceView *`.
* You should now pass what was previously the `.handle` field of `libra_source_image_d3d11_t` field directly as `image` to `libra_d3d11_filter_chain_frame`.
* The `libra_source_image_d3d11_t` struct has been removed.
* In `libra_d3d11_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
* The `chain` parameter of `libra_d3d11_filter_chain_get_param` has been made `const`.
* It is always thread safe to call `libra_d3d11_filter_chain_set_param` from any thread [^1].
## `LIBRA_RUNTIME_D3D12` changes
The following changes are applicable if `LIBRA_RUNTIME_D3D12` is defined.
* The lifetime of resources will not be extended past the call to the `libra_d3d12_filter_chain_frame` function. In other words, the refcount for any resources passed into the function
will no longer be changed, and it is explicitly the responsibility of the caller to ensure any resources remain alive until the `ID3D12GraphicsCommandList` provided is submitted.
* The fields `format`, `width`, and `height` have been removed from `libra_source_image_d3d12_t`.
* The field `descriptor` now comes before the field `resource` in the layout of `libra_source_image_d3d12_t`.
* The fields `width` and `height` have been added to `libra_output_image_d3d12_t`.
* You should now pass what was previously `.width` and `.height` of `libra_viewport_t` to these new fields in `libra_output_image_d3d12_t`.
* The `image` parameter of `libra_d3d12_filter_chain_frame` has changed from `libra_source_image_d3d12_t` to `libra_image_d3d12_t`.
* To maintain the previous behaviour, `.image_type` of the `libra_image_d3d12_t` should be set to `LIBRA_D3D12_IMAGE_TYPE_SOURCE_IMAGE`, and `.handle.source` should be the `libra_source_image_d3d12_t`struct.
* The `out` parameter of `libra_d3d12_filter_chain_frame` has changed from `libra_output_image_d3d11_t` to `libra_image_d3d12_t`.
* To maintain the previous behaviour, `.image_type` of the `libra_image_d3d12_t` should be set to `LIBRA_D3D12_IMAGE_TYPE_OUTPUT_IMAGE`, and `.handle.output` should be the `libra_output_image_d3d12_t`struct.
* Any `libra_image_d3d12_t` can now optionally pass only the `ID3D12Resource *` for the texture by setting `.image_type` to `LIBRA_D3D12_IMAGE_TYPE_RESOURCE` and setting `.handle.resource` to the resource pointer.
* If using `LIBRA_D3D12_IMAGE_TYPE_RESOURCE`, shader resource view and render target view descriptors for the input and output images will be internally allocated by the filter chain. This may result in marginally worse performance.
* In `libra_d3d12_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
* The `chain` parameter of `libra_d3d12_filter_chain_get_param` has been made `const`.
* It is always thread safe to call `libra_d3d12_filter_chain_set_param` from any thread [^1].
## `LIBRA_RUNTIME_D3D9` changes
The following changes are applicable if `LIBRA_RUNTIME_D3D9` is defined.
* In `libra_d3d9_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
* The `chain` parameter of `libra_d3d9_filter_chain_get_param` has been made `const`.
* It is always thread safe to call `libra_d3d9_filter_chain_set_param` from any thread [^1].
## `LIBRA_RUNTIME_METAL` changes
The following changes are applicable if `LIBRA_RUNTIME_METAL` is defined.
* In `libra_mtl_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
* The `chain` parameter of `libra_mtl_filter_chain_get_param` has been made `const`.
* It is always thread safe to call `libra_mtl_filter_chain_set_param` from any thread [^1].
## `libra_viewport_t` changes
All `viewport` parameters for `libra_*_filter_chain_frame` now take a *pointer* to a `libra_viewport_t` struct. In ABI 1, the semantics of `libra_viewport_t`
was unspecified (but not undefined behaviour) if `width` and `height` did not match the width and height of the output texture.
This caused confusion as to what the actual purpose of the `width` and `height` fields were. The behaviour differed across runtimes:
In some runtimes, it specified the size of the output texture, in others they were used to set the clipping rect for the render target.
In ABI 2, the semantics of `viewport` as a parameter in `libra_*_filter_chain_frame` are as specified.
* If `viewport` is `NULL`, then this will be the same as the **specified** behaviour in ABI 1&mdash;that is, as if the behaviour where `width` and `height` of `libra_viewport_t` were **equal** to that of the output texture.
* In other words, if `viewport` is `NULL`, then it will set the render viewport to be equal to the width and height of the output texture.
* If `viewport` is not `NULL`, then the following occurs.
* The width and height of the viewport rectangle for the output quad will be set to `viewport.width` and `viewport.height` respectively.
* The origin point of the viewport rectangle will be set to (`x`, `y`), **in the native coordinate system of the runtime**.
* This behaviour may change over an _API_ version bump. **API version 1 will always retain the behaviour specified here.**
* The scissor rectangle will be set to the same size and origin as the viewport rectangle.
[^1]: This has been the case since librashader 0.4.0 on ABI 1. ABI 2 codifies this guarantee: any loosening in the thread-safety guarantees of `libra_*_filter_chain_set_param` in the future may only change across an _API_ version bump. **API version 1 will always retain the behaviour specified here.**

119
README.md
View file

@ -1,6 +1,6 @@
# librashader
![Mega Bezel SMOOTH-ADV](shader_triangle.png)
![Mega Bezel SMOOTH-ADV](https://raw.githubusercontent.com/SnowflakePowered/librashader/master/shader_triangle.png)
<small>*Mega Bezel SMOOTH-ADV on DirectX 11*</small>
@ -8,7 +8,7 @@ librashader (*/ˈli:brəʃeɪdɚ/*) is a preprocessor, compiler, and runtime for
[![Latest Version](https://img.shields.io/crates/v/librashader.svg)](https://crates.io/crates/librashader) [![Docs](https://docs.rs/librashader/badge.svg)](https://docs.rs/librashader) [![build result](https://build.opensuse.org/projects/home:chyyran:librashader/packages/librashader/badge.svg)](https://software.opensuse.org//download.html?project=home%3Achyyran%3Alibrashader&package=librashader)
![License](https://img.shields.io/crates/l/librashader)
![Nightly rust](https://img.shields.io/badge/rust-nightly-orange.svg)
![Stable rust](https://img.shields.io/badge/rust-1.78-blue.svg) ![Nightly rust](https://img.shields.io/badge/rust-nightly-orange.svg)
## Installation
For end-users, librashader is available from the [Open Build Service](https://software.opensuse.org//download.html?project=home%3Achyyran%3Alibrashader&package=librashader) for a variety of Linux distributions and platforms.
@ -18,25 +18,26 @@ Windows and macOS users can grab the latest binaries from [GitHub Releases](http
librashader supports all modern graphics runtimes, including wgpu, Vulkan, OpenGL 3.3+ and 4.6 (with DSA),
Direct3D 11, Direct3D 12, and Metal.
librashader does not support legacy render APIs such as older versions of OpenGL or Direct3D, except for experimental
librashader does not support legacy render APIs such as older versions of OpenGL or Direct3D, except for limited
support for Direct3D 9.
| **API** | **Status** | **`librashader` feature** |
|-------------|------------|---------------------------|
| OpenGL 3.3+ | ✅ | `gl` |
| OpenGL 4.6 | ✅ | `gl` |
| Vulkan | ✅ | `vk` |
| Direct3D 9 | ⚠️ | `d3d9` |
| Direct3D 11 | ✅ | `d3d11` |
| Direct3D 12 | ✅ | `d3d12` |
| Metal | ✅ | `metal` |
| wgpu | 🆗 | `wgpu` |
|-------------|------------|--------------------------|
| OpenGL 3.3+ | ✅ | `gl` |
| OpenGL 4.6 | ✅ | `gl` |
| Vulkan | ✅ | `vk` |
| Direct3D 9 | 🆗️ |`d3d9` |
| Direct3D 11 | ✅ | `d3d11` |
| Direct3D 12 | ✅ | `d3d12` |
| Metal | ✅ | `metal` |
| wgpu | 🆗 | `wgpu` |
✅ Full Support &mdash; 🆗 Secondary Support &mdash; ⚠️ Experimental Support
✅ Full Support &mdash; 🆗 Secondary Support
wgpu may not support all shaders due to restrictions from WGSL. Direct3D 9 support is experimental and does not fully
support features such as previous frame feedback or history, as well as being unable to support shaders that need Direct3D 10+
only features.
Shader compatibility is not guaranteed on render APIs with secondary support.
wgpu has restrictions on shaders that can not be converted to WGSL, such as those that use `inverse`. Direct3D 9 does not support
shaders that need Direct3D 10+ only features, or shaders that can not be compiled to [Shader Model 3.0](https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/shader-model-3).
## Usage
@ -47,7 +48,6 @@ of the internals if you wish to use parts of librashader piecemeal.
The librashader C API is best used by including `librashader_ld.h` in your project, which implements a loader that dynamically
loads the librashader (`librashader.so`, `librashader.dll`, or `librashader.dylib`) implementation in the search path.
### C compatibility
The recommended way of integrating `librashader` is by the `librashader_ld` single header library which implements
a dynamic loader for `librashader.dll` / `librashader.so` / `librashader.dylib`. See the [versioning policy](https://github.com/SnowflakePowered/librashader#versioning)
@ -74,6 +74,8 @@ is not available to OpenGL.
The Metal runtime is **not thread safe**. However you can still defer submission of GPU resource initialization through the
`filter_chain_create_deferred` function.
The Direct3D 9 API is not thread safe, unless `D3DCREATE_MULTITHREADED` is enabled at device creation.
### Quad vertices and rotations
All runtimes render intermediate passes with an identity matrix MVP and a VBO for with range `[-1, 1]`. The final pass uses a
Quad VBO with range `[0, 1]` and the following projection matrix by default.
@ -90,7 +92,39 @@ static DEFAULT_MVP: &[f32; 16] = &[
As with RetroArch, a rotation on this MVP will be applied only on the final pass for these runtimes. This is the only way to
pass orientation information to shaders.
### Building
### Writing a librashader Runtime
If you wish to contribute a runtime implementation not already available, see the [librashader-runtime](https://docs.rs/librashader-runtime/latest/librashader_runtime/)
crate for helpers and shared logic used across all librashader runtime implementations. Using these helpers and traits will
ensure that your runtime has consistent behaviour for uniform and texture semantics bindings with the existing librashader runtimes.
These types should not be exposed to the end user in the runtime's public API, and should be kept internal to the implementation of
the runtime.
## Command-line interface
librashader provides a command-line interface to reflect and debug 'slang' shaders and presets.
```
Usage: librashader-cli <COMMAND>
Commands:
render Render a shader preset against an image
compare Compare two runtimes and get a similarity score between the two runtimes rendering the same frame
parse Parse a preset and get a JSON representation of the data
pack Create a serialized preset pack from a shader preset
preprocess Get the raw GLSL output of a preprocessed shader
transpile Transpile a shader in a given preset to the given format
reflect Reflect the shader relative to a preset, giving information about semantics used in a slang shader
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
```
For more information, see [`CLI.md`](https://github.com/SnowflakePowered/librashader/blob/master/CLI.md).
## Building
For Rust projects, simply add the crate to your `Cargo.toml`.
@ -110,14 +144,28 @@ This will output a `librashader.dll` or `librashader.so` in the target folder. P
While librashader has no build-time dependencies, using `librashader_ld.h` may require headers from
the relevant runtime graphics API.
### Writing a librashader Runtime
If you wish to contribute a runtime implementation not already available, see the [librashader-runtime](https://docs.rs/librashader-runtime/latest/librashader_runtime/)
crate for helpers and shared logic used across all librashader runtime implementations. Using these helpers and traits will
ensure that your runtime has consistent behaviour for uniform and texture semantics bindings with the existing librashader runtimes.
### Building against stable Rust
While librashader is intended to be used with nightly Rust until [required features](https://github.com/SnowflakePowered/librashader/issues/55) are stabilized, it supports being
built with stable Rust with the `stable` feature.
These types should not be exposed to the end user in the runtime's public API, and should be kept internal to the implementation of
the runtime.
```toml
librashader = { features = ["stable"] }
```
If building the C API, the `--stable` flag in the build script will enable the `stable` feature.
```
cargo +stable run -p librashader-build-script -- --profile optimized --stable
```
There are some caveats when building against stable Rust, such that building librashader against nightly Rust is still highly encouraged.
* C headers will not be regenerated when building with the `stable` feature.
* There is a minor performance hit in initial shader compilation when building against stable Rust. This is due to boxed trait objects being used instead of `impl Trait`.
* A higher MSRV is required when building against stable Rust.
When the `trait_alias_impl_trait` feature is stabilized, the `stable` feature will be removed.
## Examples
@ -127,6 +175,7 @@ The following Rust examples show how to use each librashader runtime.
* [Direct3D 11](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-d3d11/tests/triangle.rs)
* [Direct3D 12](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-d3d12/tests/triangle.rs)
* [wgpu](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-wgpu/tests/hello_triangle.rs)
* [Direct3D 9](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-d3d9/tests/triangle.rs)
Some basic examples on using the C API are also provided.
@ -151,6 +200,9 @@ Please report an issue if you run into a shader that works in RetroArch, but not
`mipmap_input0 = "true"`.
* The preset parser is a substantially stricter implementation that the one in RetroArch. Not all shader presets may be
compatible. If you find this is the case, please file an issue so a workaround can be added.
* Shaders are [pre-linked at the SPIR-V level](https://github.com/SnowflakePowered/librashader/blob/master/librashader-reflect/src/front/spirv_passes/link_input_outputs.rs) before being
passed to the driver. Unused inputs in the fragment shader are removed, and the corresponding input in the vertex shader
is downgraded to a global variable.
### Runtime specific differences
* OpenGL
* Copying of in-flight framebuffer contents to history is done via `glBlitFramebuffer` rather than drawing a quad into an intermediate FBO.
@ -184,7 +236,7 @@ and are more a heads-up for integrating librashader into your project.
## Versioning
[![Latest Version](https://img.shields.io/crates/v/librashader.svg)](https://crates.io/crates/librashader)
![C ABI](https://img.shields.io/badge/ABI%20version-1-yellowgreen)
![C ABI](https://img.shields.io/badge/ABI%20version-2-yellowgreen)
![C API](https://img.shields.io/badge/API%20version-1-blue)
@ -217,14 +269,23 @@ The `SONAME` of `librashader.so` when installed via package manager is set to `L
The above does not apply to releases of librashader prior to `0.1.0`, which were allowed to break API and ABI compatibility
in both the Rust and C API without an increase to either `LIBRASHADER_CURRENT_VERSION` or `LIBRASHADER_CURRENT_ABI`.
The ABI version was bumped from `1` to `2` with librashader `0.5.0`. See [`MIGRATION-ABI2.md`](https://github.com/SnowflakePowered/librashader/blob/master/MIGRATION-ABI2.md)
for migration instructions.
### MSRV Policy
While librashader requires nightly Rust, the following MSRV policy is enforced for unstable library features.
Building against stable Rust requires the following MSRV.
* Windows and macOS: **latest** nightly
* Linux: **1.76**
* All platforms: **1.78**
A CI job runs weekly to ensure librashader continues to build on nightly. Note that the MSRV is only intended to ease distribution on Linux and is allowed to change any time. It generally tracks the latest version of Rust available in the latest version of Ubuntu, but this may change with no warning in a patch release.
When building against nightly Rust, the following MSRV policy is enforced for unstable library features.
* All platforms: **1.78**
A CI job runs weekly to ensure librashader continues to build on nightly.
Note that the MSRV is only intended to ease distribution on Linux when building against nightly Rust or with `RUSTC_BOOTSTRAP=1`, and is allowed to change any time.
It generally tracks the latest version of Rust available in the latest version of Ubuntu, but this may change with no warning in a patch release.
## License
The core parts of librashader such as the preprocessor, the preset parser,

View file

@ -33,15 +33,9 @@ libra_gl_filter_chain_t load_gl_filter_chain(libra_gl_loader_t opengl, const cha
std::cout << "Could not load preset\n";
return NULL;
}
// OpenGL runtime needs to be initialized.
if (librashader.gl_init_context(opengl) != NULL) {
std::cout << "Could not initialize OpenGL context\n";
return NULL;
}
libra_gl_filter_chain_t chain;
if (librashader.gl_filter_chain_create(&preset, NULL, &chain) {
if (librashader.gl_filter_chain_create(&preset, opengl, NULL, &chain) {
std::cout << "Could not create OpenGL filter chain\n";
}
return chain;

View file

@ -51,19 +51,52 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// The type of image passed to the image.
enum LIBRA_D3D12_IMAGE_TYPE
#ifdef __cplusplus
: int32_t
#endif // __cplusplus
{
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// The image handle is a pointer to a `ID3D12Resource`.
LIBRA_D3D12_IMAGE_TYPE_RESOURCE = 0,
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// The image handle is a `libra_source_image_d3d12_t`
LIBRA_D3D12_IMAGE_TYPE_SOURCE_IMAGE = 1,
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// The image handle is a `libra_output_image_d3d12_t`
LIBRA_D3D12_IMAGE_TYPE_OUTPUT_IMAGE = 2,
#endif
};
#ifndef __cplusplus
typedef int32_t LIBRA_D3D12_IMAGE_TYPE;
#endif // __cplusplus
#endif
/// Error codes for librashader error types.
enum LIBRA_ERRNO
#ifdef __cplusplus
: int32_t
#endif // __cplusplus
{
/// Error code for an unknown error.
LIBRA_ERRNO_UNKNOWN_ERROR = 0,
/// Error code for an invalid parameter.
LIBRA_ERRNO_INVALID_PARAMETER = 1,
/// Error code for an invalid (non-UTF8) string.
LIBRA_ERRNO_INVALID_STRING = 2,
/// Error code for a preset parser error.
LIBRA_ERRNO_PRESET_ERROR = 3,
/// Error code for a preprocessor error.
LIBRA_ERRNO_PREPROCESS_ERROR = 4,
/// Error code for a shader parameter error.
LIBRA_ERRNO_SHADER_PARAMETER_ERROR = 5,
/// Error code for a reflection error.
LIBRA_ERRNO_REFLECT_ERROR = 6,
/// Error code for a runtime error.
LIBRA_ERRNO_RUNTIME_ERROR = 7,
};
#ifndef __cplusplus
@ -76,24 +109,35 @@ enum LIBRA_PRESET_CTX_ORIENTATION
: uint32_t
#endif // __cplusplus
{
/// Context parameter for vertical orientation.
LIBRA_PRESET_CTX_ORIENTATION_VERTICAL = 0,
/// Context parameter for horizontal orientation.
LIBRA_PRESET_CTX_ORIENTATION_HORIZONTAL,
};
#ifndef __cplusplus
typedef uint32_t LIBRA_PRESET_CTX_ORIENTATION;
#endif // __cplusplus
/// An enum representing graphics runtimes (video drivers) for use in preset contexts.
enum LIBRA_PRESET_CTX_RUNTIME
#ifdef __cplusplus
: uint32_t
#endif // __cplusplus
{
/// No runtime.
LIBRA_PRESET_CTX_RUNTIME_NONE = 0,
/// OpenGL 3.3+
LIBRA_PRESET_CTX_RUNTIME_GL_CORE,
/// Vulkan
LIBRA_PRESET_CTX_RUNTIME_VULKAN,
/// Direct3D 11
LIBRA_PRESET_CTX_RUNTIME_D3D11,
/// Direct3D 12
LIBRA_PRESET_CTX_RUNTIME_D3D12,
/// Metal
LIBRA_PRESET_CTX_RUNTIME_METAL,
/// Direct3D 9
LIBRA_PRESET_CTX_RUNTIME_D3D9_HLSL,
};
#ifndef __cplusplus
typedef uint32_t LIBRA_PRESET_CTX_RUNTIME;
@ -155,11 +199,10 @@ typedef struct libra_preset_param_t {
typedef struct libra_preset_param_list_t {
/// A pointer to the parameter
const struct libra_preset_param_t *parameters;
/// The number of parameters in the list.
/// The number of parameters in the list. This field
/// is readonly, and changing it will lead to undefined
/// behaviour on free.
uint64_t length;
/// For internal use only.
/// Changing this causes immediate undefined behaviour on freeing this parameter list.
uint64_t _internal_alloc;
} libra_preset_param_list_t;
#if defined(LIBRA_RUNTIME_OPENGL)
@ -195,43 +238,33 @@ typedef struct _filter_chain_gl *libra_gl_filter_chain_t;
#endif
#if defined(LIBRA_RUNTIME_OPENGL)
/// OpenGL parameters for the source image.
typedef struct libra_source_image_gl_t {
/// A texture GLuint to the source image.
/// OpenGL parameters for an image.
typedef struct libra_image_gl_t {
/// A texture GLuint to the texture.
uint32_t handle;
/// The format of the source image.
/// The format of the texture.
uint32_t format;
/// The width of the source image.
/// The width of the texture.
uint32_t width;
/// The height of the source image.
/// The height of the texture.
uint32_t height;
} libra_source_image_gl_t;
} libra_image_gl_t;
#endif
/// Defines the output viewport for a rendered frame.
/// Defines the output origin for a rendered frame.
typedef struct libra_viewport_t {
/// The x offset in the viewport framebuffer to begin rendering from.
float x;
/// The y offset in the viewport framebuffer to begin rendering from.
float y;
/// The width of the viewport framebuffer.
/// The width extent of the viewport framebuffer to end rendering, relative to
/// the origin specified by x.
uint32_t width;
/// The height of the viewport framebuffer.
/// The height extent of the viewport framebuffer to end rendering, relative to
/// the origin specified by y.
uint32_t height;
} libra_viewport_t;
#if defined(LIBRA_RUNTIME_OPENGL)
/// OpenGL parameters for the output framebuffer.
typedef struct libra_output_framebuffer_gl_t {
/// A framebuffer GLuint to the output framebuffer.
uint32_t fbo;
/// A texture GLuint to the logical buffer of the output framebuffer.
uint32_t texture;
/// The format of the output framebuffer.
uint32_t format;
} libra_output_framebuffer_gl_t;
#endif
#if defined(LIBRA_RUNTIME_OPENGL)
/// Options for each OpenGL shader frame.
typedef struct frame_gl_opt_t {
@ -242,7 +275,7 @@ typedef struct frame_gl_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
int32_t frame_direction;
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
uint32_t rotation;
/// The total number of subframes ran. Default is 1.
uint32_t total_subframes;
@ -263,6 +296,9 @@ typedef struct libra_device_vk_t {
/// A raw `VkDevice` handle
/// for the device attached to the instance that will perform rendering.
VkDevice device;
/// The queue to use, if this is `NULL`, then
/// a suitable queue will be chosen. This must be a graphics queue.
VkQueue queue;
/// The entry loader for the Vulkan library.
PFN_vkGetInstanceProcAddr entry;
} libra_device_vk_t;
@ -293,27 +329,17 @@ typedef struct _filter_chain_vk *libra_vk_filter_chain_t;
#endif
#if defined(LIBRA_RUNTIME_VULKAN)
/// Vulkan parameters for the source image.
typedef struct libra_source_image_vk_t {
/// A raw `VkImage` handle to the source image.
/// Vulkan parameters for an image.
typedef struct libra_image_vk_t {
/// A raw `VkImage` handle.
VkImage handle;
/// The `VkFormat` of the source image.
/// The `VkFormat` of the `VkImage`.
VkFormat format;
/// The width of the source image.
/// The width of the `VkImage`.
uint32_t width;
/// The height of the source image.
/// The height of the `VkImage`.
uint32_t height;
} libra_source_image_vk_t;
#endif
#if defined(LIBRA_RUNTIME_VULKAN)
/// Vulkan parameters for the output image.
typedef struct libra_output_image_vk_t {
/// A raw `VkImage` handle to the output image.
VkImage handle;
/// The `VkFormat` of the output image.
VkFormat format;
} libra_output_image_vk_t;
} libra_image_vk_t;
#endif
#if defined(LIBRA_RUNTIME_VULKAN)
@ -326,7 +352,7 @@ typedef struct frame_vk_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
int32_t frame_direction;
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
uint32_t rotation;
/// The total number of subframes ran. Default is 1.
uint32_t total_subframes;
@ -354,18 +380,6 @@ typedef struct filter_chain_d3d11_opt_t {
typedef struct _filter_chain_d3d11 *libra_d3d11_filter_chain_t;
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11))
/// Direct3D 11 parameters for the source image.
typedef struct libra_source_image_d3d11_t {
/// A shader resource view into the source image
ID3D11ShaderResourceView * handle;
/// The width of the source image.
uint32_t width;
/// The height of the source image.
uint32_t height;
} libra_source_image_d3d11_t;
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11))
/// Options for each Direct3D 11 shader frame.
typedef struct frame_d3d11_opt_t {
@ -376,7 +390,7 @@ typedef struct frame_d3d11_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
int32_t frame_direction;
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
uint32_t rotation;
/// The total number of subframes ran. Default is 1.
uint32_t total_subframes;
@ -414,7 +428,7 @@ typedef struct frame_d3d9_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
int32_t frame_direction;
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
uint32_t rotation;
/// The total number of subframes ran. Default is 1.
uint32_t total_subframes;
@ -448,16 +462,10 @@ typedef struct _filter_chain_d3d12 *libra_d3d12_filter_chain_t;
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// Direct3D 12 parameters for the source image.
typedef struct libra_source_image_d3d12_t {
/// The resource containing the image.
ID3D12Resource * resource;
/// A CPU descriptor handle to a shader resource view of the image.
D3D12_CPU_DESCRIPTOR_HANDLE descriptor;
/// The format of the image.
DXGI_FORMAT format;
/// The width of the source image.
uint32_t width;
/// The height of the source image.
uint32_t height;
/// The resource containing the image.
ID3D12Resource * resource;
} libra_source_image_d3d12_t;
#endif
@ -468,9 +476,38 @@ typedef struct libra_output_image_d3d12_t {
D3D12_CPU_DESCRIPTOR_HANDLE descriptor;
/// The format of the image.
DXGI_FORMAT format;
/// The width of the output image.
uint32_t width;
/// The height of the output image.
uint32_t height;
} libra_output_image_d3d12_t;
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// A handle to a Direct3D 12 image.
///
/// This must be either a pointer to a `ID3D12Resource`,
/// or a valid source or output image type.
typedef union libra_image_d3d12_handle_t {
/// A pointer to an `ID3D12Resource`, with descriptors managed by the filter chain.
ID3D12Resource * resource;
/// A source image with externally managed descriptors.
struct libra_source_image_d3d12_t source;
/// An output image with externally managed descriptors.
struct libra_output_image_d3d12_t output;
} libra_image_d3d12_handle_t;
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// Tagged union for a Direct3D 12 image
typedef struct libra_image_d3d12_t {
/// The type of the image.
LIBRA_D3D12_IMAGE_TYPE image_type;
/// The handle to the image.
union libra_image_d3d12_handle_t handle;
} libra_image_d3d12_t;
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// Options for each Direct3D 12 shader frame.
typedef struct frame_d3d12_opt_t {
@ -481,7 +518,7 @@ typedef struct frame_d3d12_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
int32_t frame_direction;
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
uint32_t rotation;
/// The total number of subframes ran. Default is 1.
uint32_t total_subframes;
@ -501,6 +538,7 @@ typedef struct filter_chain_mtl_opt_t {
#endif
#if (defined(__APPLE__) && defined(LIBRA_RUNTIME_METAL) && defined(__OBJC__))
/// A handle to a Vulkan filter chain.
typedef struct _filter_chain_mtl *libra_mtl_filter_chain_t;
#endif
@ -514,7 +552,7 @@ typedef struct frame_mtl_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
int32_t frame_direction;
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
uint32_t rotation;
/// The total number of subframes ran. Default is 1.
uint32_t total_subframes;
@ -523,6 +561,7 @@ typedef struct frame_mtl_opt_t {
} frame_mtl_opt_t;
#endif
/// ABI version type alias.
typedef size_t LIBRASHADER_ABI_VERSION;
/// Function pointer definition for libra_abi_version
@ -547,7 +586,7 @@ typedef libra_error_t (*PFN_libra_preset_set_param)(libra_shader_preset_t *prese
/// Function pointer definition for
///libra_preset_get_param
typedef libra_error_t (*PFN_libra_preset_get_param)(libra_shader_preset_t *preset,
typedef libra_error_t (*PFN_libra_preset_get_param)(const libra_shader_preset_t *preset,
const char *name,
float *value);
@ -557,7 +596,7 @@ typedef libra_error_t (*PFN_libra_preset_print)(libra_shader_preset_t *preset);
/// Function pointer definition for
///libra_preset_get_runtime_params
typedef libra_error_t (*PFN_libra_preset_get_runtime_params)(libra_shader_preset_t *preset,
typedef libra_error_t (*PFN_libra_preset_get_runtime_params)(const libra_shader_preset_t *preset,
struct libra_preset_param_list_t *out);
/// Function pointer definition for
@ -644,16 +683,11 @@ typedef int32_t (*PFN_libra_error_write)(libra_error_t error, char **out);
/// Function pointer definition for libra_error_free_string
typedef int32_t (*PFN_libra_error_free_string)(char **out);
#if defined(LIBRA_RUNTIME_OPENGL)
/// Function pointer definition for
///libra_gl_init_context
typedef libra_error_t (*PFN_libra_gl_init_context)(libra_gl_loader_t loader);
#endif
#if defined(LIBRA_RUNTIME_OPENGL)
/// Function pointer definition for
///libra_gl_filter_chain_create
typedef libra_error_t (*PFN_libra_gl_filter_chain_create)(libra_shader_preset_t *preset,
libra_gl_loader_t loader,
const struct filter_chain_gl_opt_t *options,
libra_gl_filter_chain_t *out);
#endif
@ -663,9 +697,9 @@ typedef libra_error_t (*PFN_libra_gl_filter_chain_create)(libra_shader_preset_t
///libra_gl_filter_chain_frame
typedef libra_error_t (*PFN_libra_gl_filter_chain_frame)(libra_gl_filter_chain_t *chain,
size_t frame_count,
struct libra_source_image_gl_t image,
struct libra_viewport_t viewport,
struct libra_output_framebuffer_gl_t out,
struct libra_image_gl_t image,
struct libra_image_gl_t out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_gl_opt_t *opt);
#endif
@ -681,7 +715,7 @@ typedef libra_error_t (*PFN_libra_gl_filter_chain_set_param)(libra_gl_filter_cha
#if defined(LIBRA_RUNTIME_OPENGL)
/// Function pointer definition for
///libra_gl_filter_chain_get_param
typedef libra_error_t (*PFN_libra_gl_filter_chain_get_param)(libra_gl_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_gl_filter_chain_get_param)(const libra_gl_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -696,7 +730,7 @@ typedef libra_error_t (*PFN_libra_gl_filter_chain_set_active_pass_count)(libra_g
#if defined(LIBRA_RUNTIME_OPENGL)
/// Function pointer definition for
///libra_gl_filter_chain_get_active_pass_count
typedef libra_error_t (*PFN_libra_gl_filter_chain_get_active_pass_count)(libra_gl_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_gl_filter_chain_get_active_pass_count)(const libra_gl_filter_chain_t *chain,
uint32_t *out);
#endif
@ -731,9 +765,9 @@ typedef libra_error_t (*PFN_libra_vk_filter_chain_create_deferred)(libra_shader_
typedef libra_error_t (*PFN_libra_vk_filter_chain_frame)(libra_vk_filter_chain_t *chain,
VkCommandBuffer command_buffer,
size_t frame_count,
struct libra_source_image_vk_t image,
struct libra_viewport_t viewport,
struct libra_output_image_vk_t out,
struct libra_image_vk_t image,
struct libra_image_vk_t out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_vk_opt_t *opt);
#endif
@ -749,7 +783,7 @@ typedef libra_error_t (*PFN_libra_vk_filter_chain_set_param)(libra_vk_filter_cha
#if defined(LIBRA_RUNTIME_VULKAN)
/// Function pointer definition for
///libra_vk_filter_chain_get_param
typedef libra_error_t (*PFN_libra_vk_filter_chain_get_param)(libra_vk_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_vk_filter_chain_get_param)(const libra_vk_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -764,7 +798,7 @@ typedef libra_error_t (*PFN_libra_vk_filter_chain_set_active_pass_count)(libra_v
#if defined(LIBRA_RUNTIME_VULKAN)
/// Function pointer definition for
///libra_vk_filter_chain_get_active_pass_count
typedef libra_error_t (*PFN_libra_vk_filter_chain_get_active_pass_count)(libra_vk_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_vk_filter_chain_get_active_pass_count)(const libra_vk_filter_chain_t *chain,
uint32_t *out);
#endif
@ -799,9 +833,9 @@ typedef libra_error_t (*PFN_libra_d3d11_filter_chain_create_deferred)(libra_shad
typedef libra_error_t (*PFN_libra_d3d11_filter_chain_frame)(libra_d3d11_filter_chain_t *chain,
ID3D11DeviceContext * device_context,
size_t frame_count,
struct libra_source_image_d3d11_t image,
struct libra_viewport_t viewport,
ID3D11ShaderResourceView * image,
ID3D11RenderTargetView * out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_d3d11_opt_t *options);
#endif
@ -817,7 +851,7 @@ typedef libra_error_t (*PFN_libra_d3d11_filter_chain_set_param)(libra_d3d11_filt
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11))
/// Function pointer definition for
///libra_d3d11_filter_chain_get_param
typedef libra_error_t (*PFN_libra_d3d11_filter_chain_get_param)(libra_d3d11_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_d3d11_filter_chain_get_param)(const libra_d3d11_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -832,7 +866,7 @@ typedef libra_error_t (*PFN_libra_d3d11_filter_chain_set_active_pass_count)(libr
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11))
/// Function pointer definition for
///libra_d3d11_filter_chain_get_active_pass_count
typedef libra_error_t (*PFN_libra_d3d11_filter_chain_get_active_pass_count)(libra_d3d11_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_d3d11_filter_chain_get_active_pass_count)(const libra_d3d11_filter_chain_t *chain,
uint32_t *out);
#endif
@ -857,8 +891,8 @@ typedef libra_error_t (*PFN_libra_d3d9_filter_chain_create)(libra_shader_preset_
typedef libra_error_t (*PFN_libra_d3d9_filter_chain_frame)(libra_d3d9_filter_chain_t *chain,
size_t frame_count,
IDirect3DTexture9 * image,
struct libra_viewport_t viewport,
IDirect3DSurface9 * out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_d3d9_opt_t *options);
#endif
@ -874,7 +908,7 @@ typedef libra_error_t (*PFN_libra_d3d9_filter_chain_set_param)(libra_d3d9_filter
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D9))
/// Function pointer definition for
///libra_d3d9_filter_chain_get_param
typedef libra_error_t (*PFN_libra_d3d9_filter_chain_get_param)(libra_d3d9_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_d3d9_filter_chain_get_param)(const libra_d3d9_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -889,7 +923,7 @@ typedef libra_error_t (*PFN_libra_d3d9_filter_chain_set_active_pass_count)(libra
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D9))
/// Function pointer definition for
///libra_d3d9_filter_chain_get_active_pass_count
typedef libra_error_t (*PFN_libra_d3d9_filter_chain_get_active_pass_count)(libra_d3d9_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_d3d9_filter_chain_get_active_pass_count)(const libra_d3d9_filter_chain_t *chain,
uint32_t *out);
#endif
@ -924,9 +958,9 @@ typedef libra_error_t (*PFN_libra_d3d12_filter_chain_create_deferred)(libra_shad
typedef libra_error_t (*PFN_libra_d3d12_filter_chain_frame)(libra_d3d12_filter_chain_t *chain,
ID3D12GraphicsCommandList * command_list,
size_t frame_count,
struct libra_source_image_d3d12_t image,
struct libra_viewport_t viewport,
struct libra_output_image_d3d12_t out,
struct libra_image_d3d12_t image,
struct libra_image_d3d12_t out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_d3d12_opt_t *options);
#endif
@ -942,7 +976,7 @@ typedef libra_error_t (*PFN_libra_d3d12_filter_chain_set_param)(libra_d3d12_filt
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// Function pointer definition for
///libra_d3d12_filter_chain_get_param
typedef libra_error_t (*PFN_libra_d3d12_filter_chain_get_param)(libra_d3d12_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_d3d12_filter_chain_get_param)(const libra_d3d12_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -957,7 +991,7 @@ typedef libra_error_t (*PFN_libra_d3d12_filter_chain_set_active_pass_count)(libr
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// Function pointer definition for
///libra_d3d12_filter_chain_get_active_pass_count
typedef libra_error_t (*PFN_libra_d3d12_filter_chain_get_active_pass_count)(libra_d3d12_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_d3d12_filter_chain_get_active_pass_count)(const libra_d3d12_filter_chain_t *chain,
uint32_t *out);
#endif
@ -993,8 +1027,8 @@ typedef libra_error_t (*PFN_libra_mtl_filter_chain_frame)(libra_mtl_filter_chain
id<MTLCommandBuffer> command_buffer,
size_t frame_count,
id<MTLTexture> image,
struct libra_viewport_t viewport,
id<MTLTexture> output,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_mtl_opt_t *opt);
#endif
@ -1010,7 +1044,7 @@ typedef libra_error_t (*PFN_libra_mtl_filter_chain_set_param)(libra_mtl_filter_c
#if (defined(__APPLE__) && defined(LIBRA_RUNTIME_METAL) && defined(__OBJC__))
/// Function pointer definition for
///libra_mtl_filter_chain_get_param
typedef libra_error_t (*PFN_libra_mtl_filter_chain_get_param)(libra_mtl_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_mtl_filter_chain_get_param)(const libra_mtl_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1025,7 +1059,7 @@ typedef libra_error_t (*PFN_libra_mtl_filter_chain_set_active_pass_count)(libra_
#if (defined(__APPLE__) && defined(LIBRA_RUNTIME_METAL) && defined(__OBJC__))
/// Function pointer definition for
///libra_mtl_filter_chain_get_active_pass_count
typedef libra_error_t (*PFN_libra_mtl_filter_chain_get_active_pass_count)(libra_mtl_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_mtl_filter_chain_get_active_pass_count)(const libra_mtl_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1058,10 +1092,17 @@ typedef libra_error_t (*PFN_libra_mtl_filter_chain_free)(libra_mtl_filter_chain_
/// ABI versions are not backwards compatible. It is not
/// valid to load a librashader C API instance for any ABI
/// version not equal to LIBRASHADER_CURRENT_ABI.
///
/// ## ABI Versions
/// - ABI version 0: null instance (unloaded)
/// - ABI version 1: 0.1.0
#define LIBRASHADER_CURRENT_ABI 1
/// - ABI version 2: 0.5.0
/// - Reduced texture size information needed for some runtimes.
/// - Removed wrapper structs for Direct3D 11 SRV and RTV handles.
/// - Removed `gl_context_init`.
/// - Make viewport handling consistent across runtimes, which are now
/// span the output render target if omitted.
#define LIBRASHADER_CURRENT_ABI 2
#ifdef __cplusplus
extern "C" {
@ -1155,7 +1196,9 @@ libra_error_t libra_preset_set_param(libra_shader_preset_t *preset, const char *
/// - `preset` must be null or a valid and aligned pointer to a shader preset.
/// - `name` must be null or a valid and aligned pointer to a string.
/// - `value` may be a pointer to a uninitialized `float`.
libra_error_t libra_preset_get_param(libra_shader_preset_t *preset, const char *name, float *value);
libra_error_t libra_preset_get_param(const libra_shader_preset_t *preset,
const char *name,
float *value);
/// Pretty print the shader preset.
///
@ -1173,7 +1216,7 @@ libra_error_t libra_preset_print(libra_shader_preset_t *preset);
/// cause undefined behaviour when later freed.
/// - It is safe to call `libra_preset_get_runtime_params` multiple times, however
/// the output struct must only be freed once per call.
libra_error_t libra_preset_get_runtime_params(libra_shader_preset_t *preset,
libra_error_t libra_preset_get_runtime_params(const libra_shader_preset_t *preset,
struct libra_preset_param_list_t *out);
/// Free the runtime parameters.
@ -1197,20 +1240,6 @@ libra_error_t libra_preset_get_runtime_params(libra_shader_preset_t *preset,
/// in undefined behaviour.
libra_error_t libra_preset_free_runtime_params(struct libra_preset_param_list_t preset);
#if defined(LIBRA_RUNTIME_OPENGL)
/// Initialize the OpenGL Context for librashader.
///
/// This only has to be done once throughout the lifetime of the application,
/// unless for whatever reason you switch OpenGL loaders mid-flight.
///
/// ## Safety
/// Attempting to create a filter chain will fail if the GL context is not initialized.
///
/// Reinitializing the OpenGL context with a different loader immediately invalidates previous filter
/// chain objects, and drawing with them causes immediate undefined behaviour.
libra_error_t libra_gl_init_context(libra_gl_loader_t loader);
#endif
#if defined(LIBRA_RUNTIME_OPENGL)
/// Create the filter chain given the shader preset.
///
@ -1222,6 +1251,7 @@ libra_error_t libra_gl_init_context(libra_gl_loader_t loader);
/// - `options` must be either null, or valid and aligned.
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
libra_error_t libra_gl_filter_chain_create(libra_shader_preset_t *preset,
libra_gl_loader_t loader,
const struct filter_chain_gl_opt_t *options,
libra_gl_filter_chain_t *out);
#endif
@ -1229,6 +1259,23 @@ libra_error_t libra_gl_filter_chain_create(libra_shader_preset_t *preset,
#if defined(LIBRA_RUNTIME_OPENGL)
/// Draw a frame with the given parameters for the given filter chain.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `libra_image_gl_t`, containing the name of a Texture, format, and size information to
/// to an image that will serve as the source image for the frame.
/// - `out` is a `libra_output_framebuffer_gl_t`, containing the name of a Framebuffer, the name of a Texture, format,
/// and size information for the render target of the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
@ -1242,9 +1289,9 @@ libra_error_t libra_gl_filter_chain_create(libra_shader_preset_t *preset,
/// the filter chain.
libra_error_t libra_gl_filter_chain_frame(libra_gl_filter_chain_t *chain,
size_t frame_count,
struct libra_source_image_gl_t image,
struct libra_viewport_t viewport,
struct libra_output_framebuffer_gl_t out,
struct libra_image_gl_t image,
struct libra_image_gl_t out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_gl_opt_t *opt);
#endif
@ -1268,7 +1315,7 @@ libra_error_t libra_gl_filter_chain_set_param(libra_gl_filter_chain_t *chain,
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
libra_error_t libra_gl_filter_chain_get_param(libra_gl_filter_chain_t *chain,
libra_error_t libra_gl_filter_chain_get_param(const libra_gl_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1287,7 +1334,7 @@ libra_error_t libra_gl_filter_chain_set_active_pass_count(libra_gl_filter_chain_
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
libra_error_t libra_gl_filter_chain_get_active_pass_count(libra_gl_filter_chain_t *chain,
libra_error_t libra_gl_filter_chain_get_active_pass_count(const libra_gl_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1297,6 +1344,7 @@ libra_error_t libra_gl_filter_chain_get_active_pass_count(libra_gl_filter_chain_
/// The resulting value in `chain` then becomes null.
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
/// - The context that the filter chain was initialized with **must be current** before freeing the filter chain.
libra_error_t libra_gl_filter_chain_free(libra_gl_filter_chain_t *chain);
#endif
@ -1347,13 +1395,31 @@ libra_error_t libra_vk_filter_chain_create_deferred(libra_shader_preset_t *prese
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command buffer.
///
/// * The input image must be in the `VK_SHADER_READ_ONLY_OPTIMAL` layout.
/// * The output image must be in `VK_COLOR_ATTACHMENT_OPTIMAL` layout.
///
/// librashader **will not** create a pipeline barrier for the final pass. The output image will
/// remain in `VK_COLOR_ATTACHMENT_OPTIMAL` after all shader passes. The caller must transition
/// A pipeline barrier **will not** be created for the final pass. The output image must be
/// in `VK_COLOR_ATTACHMENT_OPTIMAL`, and will remain so after all shader passes. The caller must transition
/// the output image to the final layout.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `command_buffer` is a `VkCommandBuffer` handle to record draw commands to.
/// The provided command buffer must be ready for recording and contain no prior commands
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `libra_image_vk_t`, containing a `VkImage` handle, it's format and size information,
/// to an image that will serve as the source image for the frame. The input image must be in
/// the `VK_SHADER_READ_ONLY_OPTIMAL` layout.
/// - `out` is a `libra_image_vk_t`, containing a `VkImage` handle, it's format and size information,
/// for the render target of the frame. The output image must be in `VK_COLOR_ATTACHMENT_OPTIMAL` layout.
/// The output image will remain in `VK_COLOR_ATTACHMENT_OPTIMAL` after all shader passes.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `libra_vk_filter_chain_frame` **must not be called within a RenderPass**.
/// - `command_buffer` must be a valid handle to a `VkCommandBuffer` that is ready for recording.
@ -1368,9 +1434,9 @@ libra_error_t libra_vk_filter_chain_create_deferred(libra_shader_preset_t *prese
libra_error_t libra_vk_filter_chain_frame(libra_vk_filter_chain_t *chain,
VkCommandBuffer command_buffer,
size_t frame_count,
struct libra_source_image_vk_t image,
struct libra_viewport_t viewport,
struct libra_output_image_vk_t out,
struct libra_image_vk_t image,
struct libra_image_vk_t out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_vk_opt_t *opt);
#endif
@ -1394,7 +1460,7 @@ libra_error_t libra_vk_filter_chain_set_param(libra_vk_filter_chain_t *chain,
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_vk_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
libra_error_t libra_vk_filter_chain_get_param(libra_vk_filter_chain_t *chain,
libra_error_t libra_vk_filter_chain_get_param(const libra_vk_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1413,7 +1479,7 @@ libra_error_t libra_vk_filter_chain_set_active_pass_count(libra_vk_filter_chain_
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_vk_filter_chain_t`.
libra_error_t libra_vk_filter_chain_get_active_pass_count(libra_vk_filter_chain_t *chain,
libra_error_t libra_vk_filter_chain_get_active_pass_count(const libra_vk_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1478,9 +1544,26 @@ libra_error_t libra_d3d11_filter_chain_create_deferred(libra_shader_preset_t *pr
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11))
/// Draw a frame with the given parameters for the given filter chain.
///
/// If `device_context` is null, then commands are recorded onto the immediate context. Otherwise,
/// it will record commands onto the provided context. If the context is deferred, librashader
/// will not finalize command lists. The context must otherwise be associated with the `ID3D11Device`
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `device_context` is the ID3D11DeviceContext used to record draw commands to.
/// If `device_context` is null, then commands are recorded onto the immediate context. Otherwise,
/// it will record commands onto the provided context. If the context is deferred, librashader
/// will not finalize command lists. The context must otherwise be associated with the `ID3D11Device`
/// the filter chain was created with.
///
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a pointer to a `ID3D11ShaderResourceView` that will serve as the source image for the frame.
/// - `out` is a pointer to a `ID3D11RenderTargetView` that will serve as the render target for the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
@ -1499,9 +1582,9 @@ libra_error_t libra_d3d11_filter_chain_create_deferred(libra_shader_preset_t *pr
libra_error_t libra_d3d11_filter_chain_frame(libra_d3d11_filter_chain_t *chain,
ID3D11DeviceContext * device_context,
size_t frame_count,
struct libra_source_image_d3d11_t image,
struct libra_viewport_t viewport,
ID3D11ShaderResourceView * image,
ID3D11RenderTargetView * out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_d3d11_opt_t *options);
#endif
@ -1525,7 +1608,7 @@ libra_error_t libra_d3d11_filter_chain_set_param(libra_d3d11_filter_chain_t *cha
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d11_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
libra_error_t libra_d3d11_filter_chain_get_param(libra_d3d11_filter_chain_t *chain,
libra_error_t libra_d3d11_filter_chain_get_param(const libra_d3d11_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1544,7 +1627,7 @@ libra_error_t libra_d3d11_filter_chain_set_active_pass_count(libra_d3d11_filter_
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d11_filter_chain_t`.
libra_error_t libra_d3d11_filter_chain_get_active_pass_count(libra_d3d11_filter_chain_t *chain,
libra_error_t libra_d3d11_filter_chain_get_active_pass_count(const libra_d3d11_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1577,29 +1660,37 @@ libra_error_t libra_d3d9_filter_chain_create(libra_shader_preset_t *preset,
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D9))
/// Draw a frame with the given parameters for the given filter chain.
///
/// If `device_context` is null, then commands are recorded onto the immediate context. Otherwise,
/// it will record commands onto the provided context. If the context is deferred, librashader
/// will not finalize command lists. The context must otherwise be associated with the `Id3d9Device`
/// ## Parameters
/// - `chain` is a handle to the filter chain.
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a pointer to a `IDirect3DTexture9` that will serve as the source image for the frame.
/// - `out` is a pointer to a `IDirect3DSurface9` that will serve as the render target for the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
/// - `viewport` may be null, or if it is not null, must be an aligned pointer to an instance of `libra_viewport_t`.
/// - `mvp` may be null, or if it is not null, must be an aligned pointer to 16 consecutive `float`
/// values for the model view projection matrix.
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_d3d9_opt_t`
/// struct.
/// - `out` must not be null.
/// - `image.handle` must not be null.
/// - If `device_context` is null, commands will be recorded onto the immediate context of the `Id3d9Device`
/// this filter chain was created with. The context must otherwise be associated with the `Id3d9Device`
/// the filter chain was created with.
/// - `image` must not be null.
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
/// thread at a time may call this function.
libra_error_t libra_d3d9_filter_chain_frame(libra_d3d9_filter_chain_t *chain,
size_t frame_count,
IDirect3DTexture9 * image,
struct libra_viewport_t viewport,
IDirect3DSurface9 * out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_d3d9_opt_t *options);
#endif
@ -1623,7 +1714,7 @@ libra_error_t libra_d3d9_filter_chain_set_param(libra_d3d9_filter_chain_t *chain
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
libra_error_t libra_d3d9_filter_chain_get_param(libra_d3d9_filter_chain_t *chain,
libra_error_t libra_d3d9_filter_chain_get_param(const libra_d3d9_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1642,7 +1733,7 @@ libra_error_t libra_d3d9_filter_chain_set_active_pass_count(libra_d3d9_filter_ch
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
libra_error_t libra_d3d9_filter_chain_get_active_pass_count(libra_d3d9_filter_chain_t *chain,
libra_error_t libra_d3d9_filter_chain_get_active_pass_count(const libra_d3d9_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1700,13 +1791,39 @@ libra_error_t libra_d3d12_filter_chain_create_deferred(libra_shader_preset_t *pr
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command list.
///
/// * The input image must be in the `D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE` resource state.
/// * The output image must be in `D3D12_RESOURCE_STATE_RENDER_TARGET` resource state.
///
/// librashader **will not** create a resource barrier for the final pass. The output image will
/// A resource barrier **will not** be created for the final pass. The output image will
/// remain in `D3D12_RESOURCE_STATE_RENDER_TARGET` after all shader passes. The caller must transition
/// the output image to the final resource state.
///
/// The refcount of any COM pointers passed into this frame will not be changed. It is the responsibility
/// of the caller to ensure any resources remain alive until the `ID3D12GraphicsCommandList` provided is
/// submitted.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `command_list` is a `ID3D12GraphicsCommandList` to record draw commands to.
/// The provided command list must be open and associated with the `ID3D12Device` this filter chain was created with.
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `libra_image_d3d12_t` with `image_type` set to `IMAGE_TYPE_SOURCE_IMAGE` or `IMAGE_TYPE_RESOURCE`,
/// with `image_handle` either a `ID3D12Resource*` or an `libra_source_image_d3d12_t` containing a CPU descriptor to a shader resource view,
/// and the image resource the view is of, which will serve as the source image for the frame.
/// The input image resource must be in the `D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE` resource state
/// or equivalent barrier layout. The image resource must have dimension `D3D12_RESOURCE_DIMENSION_TEXTURE2D`.
/// - `out` is a `libra_image_d3d12_t`, with `image_type` set to `IMAGE_TYPE_OUTPUT_IMAGE` or `IMAGE_TYPE_RESOURCE`,
/// with `image_handle` being either a `ID3D12Resource*` or an `libra_output_image_d3d12_t`, containing a CPU descriptor handle,
/// format, and size information for the render target of the frame. The output image must be in
/// `D3D12_RESOURCE_STATE_RENDER_TARGET` resource state or equivalent barrier layout.
/// The image resource must have dimension `D3D12_RESOURCE_DIMENSION_TEXTURE2D`.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
@ -1714,18 +1831,23 @@ libra_error_t libra_d3d12_filter_chain_create_deferred(libra_shader_preset_t *pr
/// values for the model view projection matrix.
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_d3d12_opt_t`
/// struct.
/// - `out` must be a descriptor handle to a render target view.
/// - `image.resource` must not be null.
/// - Any resource pointers contained within a `libra_image_d3d12_t` must be non-null.
/// - The `handle` field of any `libra_image_d3d12_t` must be valid for it's `image_type`.
/// - If `image_type` is `IMAGE_TYPE_RESOURCE`, then `handle` must be `ID3D12Resource *`.
/// - If `image_type` is `IMAGE_TYPE_SOURCE`, then `handle` must be `libra_source_image_d3d12_t`.
/// - If `image_type` is `IMAGE_TYPE_OUTPUT`, then `handle` must be `libra_output_image_d3d12_t`.
/// - `command_list` must be a non-null pointer to a `ID3D12GraphicsCommandList` that is open,
/// and must be associated with the `ID3D12Device` this filter chain was created with.
/// - All resource pointers contained within a `libra_image_d3d12_t` must remain valid until the `ID3D12GraphicsCommandList *`
/// provided is submitted after the call to this function.
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
/// thread at a time may call this function.
libra_error_t libra_d3d12_filter_chain_frame(libra_d3d12_filter_chain_t *chain,
ID3D12GraphicsCommandList * command_list,
size_t frame_count,
struct libra_source_image_d3d12_t image,
struct libra_viewport_t viewport,
struct libra_output_image_d3d12_t out,
struct libra_image_d3d12_t image,
struct libra_image_d3d12_t out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_d3d12_opt_t *options);
#endif
@ -1749,7 +1871,7 @@ libra_error_t libra_d3d12_filter_chain_set_param(libra_d3d12_filter_chain_t *cha
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d12_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
libra_error_t libra_d3d12_filter_chain_get_param(libra_d3d12_filter_chain_t *chain,
libra_error_t libra_d3d12_filter_chain_get_param(const libra_d3d12_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1768,7 +1890,7 @@ libra_error_t libra_d3d12_filter_chain_set_active_pass_count(libra_d3d12_filter_
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d12_filter_chain_t`.
libra_error_t libra_d3d12_filter_chain_get_active_pass_count(libra_d3d12_filter_chain_t *chain,
libra_error_t libra_d3d12_filter_chain_get_active_pass_count(const libra_d3d12_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1829,6 +1951,22 @@ libra_error_t libra_mtl_filter_chain_create_deferred(libra_shader_preset_t *pres
#if (defined(__APPLE__) && defined(LIBRA_RUNTIME_METAL) && defined(__OBJC__))
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command buffer.
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `command_buffer` is a `MTLCommandBuffer` handle to record draw commands to.
/// The provided command buffer must be ready for encoding and contain no prior commands
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `id<MTLTexture>` that will serve as the source image for the frame.
/// - `out` is a `id<MTLTexture>` that is the render target of the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `command_buffer` must be a valid reference to a `MTLCommandBuffer` that is not already encoding.
@ -1844,8 +1982,8 @@ libra_error_t libra_mtl_filter_chain_frame(libra_mtl_filter_chain_t *chain,
id<MTLCommandBuffer> command_buffer,
size_t frame_count,
id<MTLTexture> image,
struct libra_viewport_t viewport,
id<MTLTexture> output,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_mtl_opt_t *opt);
#endif
@ -1869,7 +2007,7 @@ libra_error_t libra_mtl_filter_chain_set_param(libra_mtl_filter_chain_t *chain,
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
libra_error_t libra_mtl_filter_chain_get_param(libra_mtl_filter_chain_t *chain,
libra_error_t libra_mtl_filter_chain_get_param(const libra_mtl_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1888,7 +2026,7 @@ libra_error_t libra_mtl_filter_chain_set_active_pass_count(libra_mtl_filter_chai
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
libra_error_t libra_mtl_filter_chain_get_active_pass_count(libra_mtl_filter_chain_t *chain,
libra_error_t libra_mtl_filter_chain_get_active_pass_count(const libra_mtl_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1917,7 +2055,7 @@ LIBRASHADER_API_VERSION libra_instance_api_version(void);
///
/// These automatically inferred variables, as well as all other variables can be overridden with
/// `libra_preset_ctx_set_param`, but the expected string values must be provided.
/// See https://github.com/libretro/RetroArch/pull/15023 for a list of expected string values.
/// See <https://github.com/libretro/RetroArch/pull/15023> for a list of expected string values.
///
/// No variables can be removed once added to the context, however subsequent calls to set the same
/// variable will overwrite the expected variable.
@ -1971,6 +2109,8 @@ libra_error_t libra_preset_ctx_set_param(libra_preset_ctx_t *context,
/// - GLCore
/// - Direct3D11
/// - Direct3D12
/// - Metal
/// - Direct3D9 (HLSL)
///
/// This will also set the appropriate video driver extensions.
///
@ -2029,7 +2169,7 @@ libra_error_t libra_preset_ctx_set_core_aspect_orientation(libra_preset_ctx_t *c
LIBRA_PRESET_CTX_ORIENTATION value);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __LIBRASHADER_H__ */
#endif /* __LIBRASHADER_H__ */

View file

@ -35,6 +35,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// #if defined(_WIN32)
// #define LIBRA_RUNTIME_D3D11
// #define LIBRA_RUNTIME_D3D12
// #define LIBRA_RUNTIME_D3D9
// #endif
// #if (defined(__APPLE__) && defined(__OBJC__))
@ -178,7 +179,7 @@ libra_error_t __librashader__noop_preset_set_param(
}
libra_error_t __librashader__noop_preset_get_param(
libra_shader_preset_t *preset, const char *name, float *value) {
const libra_shader_preset_t *preset, const char *name, float *value) {
return NULL;
}
@ -186,7 +187,7 @@ libra_error_t __librashader__noop_preset_print(libra_shader_preset_t *preset) {
return NULL;
}
libra_error_t __librashader__noop_preset_get_runtime_params(
libra_shader_preset_t *preset, struct libra_preset_param_list_t *out) {
const libra_shader_preset_t *preset, struct libra_preset_param_list_t *out) {
return NULL;
}
libra_error_t __librashader__noop_preset_free_runtime_params(
@ -194,12 +195,9 @@ libra_error_t __librashader__noop_preset_free_runtime_params(
return NULL;
}
#if defined(LIBRA_RUNTIME_OPENGL)
libra_error_t __librashader__noop_gl_init_context(libra_gl_loader_t loader) {
return NULL;
}
libra_error_t __librashader__noop_gl_filter_chain_create(
libra_shader_preset_t *preset, const struct filter_chain_gl_opt_t *options,
libra_shader_preset_t *preset, libra_gl_loader_t loader,
const struct filter_chain_gl_opt_t *options,
libra_gl_filter_chain_t *out) {
*out = NULL;
return NULL;
@ -207,8 +205,8 @@ libra_error_t __librashader__noop_gl_filter_chain_create(
libra_error_t __librashader__noop_gl_filter_chain_frame(
libra_gl_filter_chain_t *chain, size_t frame_count,
struct libra_source_image_gl_t image, struct libra_viewport_t viewport,
struct libra_output_framebuffer_gl_t out, const float *mvp,
struct libra_image_gl_t image, struct libra_image_gl_t out,
const struct libra_viewport_t *viewport, const float *mvp,
const struct frame_gl_opt_t *opt) {
return NULL;
}
@ -224,7 +222,7 @@ libra_error_t __librashader__noop_gl_filter_chain_set_param(
}
libra_error_t __librashader__noop_gl_filter_chain_get_param(
libra_gl_filter_chain_t *chain, const char *param_name, float *out) {
const libra_gl_filter_chain_t *chain, const char *param_name, float *out) {
return NULL;
}
@ -234,7 +232,7 @@ libra_error_t __librashader__noop_gl_filter_chain_set_active_pass_count(
}
libra_error_t __librashader__noop_gl_filter_chain_get_active_pass_count(
libra_gl_filter_chain_t *chain, uint32_t *out) {
const libra_gl_filter_chain_t *chain, uint32_t *out) {
return NULL;
}
#endif
@ -257,9 +255,9 @@ libra_error_t __librashader__noop_vk_filter_chain_create_deferred(
libra_error_t __librashader__noop_vk_filter_chain_frame(
libra_vk_filter_chain_t *chain, VkCommandBuffer command_buffer,
size_t frame_count, struct libra_source_image_vk_t image,
struct libra_viewport_t viewport, struct libra_output_image_vk_t out,
const float *mvp, const struct frame_vk_opt_t *opt) {
size_t frame_count, struct libra_image_vk_t image, struct libra_image_vk_t out,
const struct libra_viewport_t *viewport, const float *mvp,
const struct frame_vk_opt_t *opt) {
return NULL;
}
@ -274,7 +272,7 @@ libra_error_t __librashader__noop_vk_filter_chain_set_param(
}
libra_error_t __librashader__noop_vk_filter_chain_get_param(
libra_vk_filter_chain_t *chain, const char *param_name, float *out) {
const libra_vk_filter_chain_t *chain, const char *param_name, float *out) {
return NULL;
}
@ -284,7 +282,7 @@ libra_error_t __librashader__noop_vk_filter_chain_set_active_pass_count(
}
libra_error_t __librashader__noop_vk_filter_chain_get_active_pass_count(
libra_vk_filter_chain_t *chain, uint32_t *out) {
const libra_vk_filter_chain_t *chain, uint32_t *out) {
return NULL;
}
#endif
@ -309,9 +307,9 @@ libra_error_t __librashader__noop_d3d11_filter_chain_create_deferred(
libra_error_t __librashader__noop_d3d11_filter_chain_frame(
libra_d3d11_filter_chain_t *chain, ID3D11DeviceContext *device_context,
size_t frame_count, struct libra_source_image_d3d11_t image,
struct libra_viewport_t viewport, ID3D11RenderTargetView *out,
const float *mvp, const struct frame_d3d11_opt_t *opt) {
size_t frame_count, ID3D11ShaderResourceView *image, ID3D11RenderTargetView *out,
const struct libra_viewport_t *viewport, const float *mvp,
const struct frame_d3d11_opt_t *opt) {
return NULL;
}
@ -326,7 +324,7 @@ libra_error_t __librashader__noop_d3d11_filter_chain_set_param(
}
libra_error_t __librashader__noop_d3d11_filter_chain_get_param(
libra_d3d11_filter_chain_t *chain, const char *param_name, float *out) {
const libra_d3d11_filter_chain_t *chain, const char *param_name, float *out) {
return NULL;
}
@ -336,7 +334,7 @@ libra_error_t __librashader__noop_d3d11_filter_chain_set_active_pass_count(
}
libra_error_t __librashader__noop_d3d11_filter_chain_get_active_pass_count(
libra_d3d11_filter_chain_t *chain, uint32_t *out) {
const libra_d3d11_filter_chain_t *chain, uint32_t *out) {
return NULL;
}
#endif
@ -361,9 +359,9 @@ libra_error_t __librashader__noop_d3d12_filter_chain_create_deferred(
libra_error_t __librashader__noop_d3d12_filter_chain_frame(
libra_d3d12_filter_chain_t *chain, ID3D12GraphicsCommandList *command_list,
size_t frame_count, struct libra_source_image_d3d12_t image,
struct libra_viewport_t viewport, struct libra_output_image_d3d12_t out,
const float *mvp, const struct frame_d3d12_opt_t *opt) {
size_t frame_count, struct libra_image_d3d12_t image, struct libra_image_d3d12_t out,
const struct libra_viewport_t *viewport, const float *mvp,
const struct frame_d3d12_opt_t *opt) {
return NULL;
}
@ -378,7 +376,7 @@ libra_error_t __librashader__noop_d3d12_filter_chain_set_param(
}
libra_error_t __librashader__noop_d3d12_filter_chain_get_param(
libra_d3d12_filter_chain_t *chain, const char *param_name, float *out) {
const libra_d3d12_filter_chain_t *chain, const char *param_name, float *out) {
return NULL;
}
@ -388,7 +386,7 @@ libra_error_t __librashader__noop_d3d12_filter_chain_set_active_pass_count(
}
libra_error_t __librashader__noop_d3d12_filter_chain_get_active_pass_count(
libra_d3d12_filter_chain_t *chain, uint32_t *out) {
const libra_d3d12_filter_chain_t *chain, uint32_t *out) {
return NULL;
}
#endif
@ -404,8 +402,8 @@ libra_error_t __librashader__noop_d3d9_filter_chain_create(
libra_error_t __librashader__noop_d3d9_filter_chain_frame(
libra_d3d9_filter_chain_t *chain, size_t frame_count,
IDirect3DTexture9 *image, struct libra_viewport_t viewport,
IDirect3DSurface9 * out, const float *mvp,
IDirect3DTexture9 *image, IDirect3DSurface9 * out,
const struct libra_viewport_t *viewport, const float *mvp,
const struct frame_d3d9_opt_t *opt) {
return NULL;
}
@ -421,7 +419,7 @@ libra_error_t __librashader__noop_d3d9_filter_chain_set_param(
}
libra_error_t __librashader__noop_d3d9_filter_chain_get_param(
libra_d3d9_filter_chain_t *chain, const char *param_name, float *out) {
const libra_d3d9_filter_chain_t *chain, const char *param_name, float *out) {
return NULL;
}
@ -431,7 +429,7 @@ libra_error_t __librashader__noop_d3d9_filter_chain_set_active_pass_count(
}
libra_error_t __librashader__noop_d3d9_filter_chain_get_active_pass_count(
libra_d3d9_filter_chain_t *chain, uint32_t *out) {
const libra_d3d9_filter_chain_t *chain, uint32_t *out) {
return NULL;
}
#endif
@ -456,8 +454,8 @@ libra_error_t __librashader__noop_mtl_filter_chain_create_deferred(
libra_error_t __librashader__noop_mtl_filter_chain_frame(
libra_mtl_filter_chain_t *chain, id<MTLCommandBuffer> command_buffer,
size_t frame_count, id<MTLTexture> image, struct libra_viewport_t viewport,
id<MTLTexture> output, const float *mvp,
size_t frame_count, id<MTLTexture> image, id<MTLTexture> output,
const struct libra_viewport_t *viewport, const float *mvp,
const struct frame_mtl_opt_t *opt) {
return NULL;
}
@ -473,7 +471,7 @@ libra_error_t __librashader__noop_mtl_filter_chain_set_param(
}
libra_error_t __librashader__noop_mtl_filter_chain_get_param(
libra_mtl_filter_chain_t *chain, const char *param_name, float *out) {
const libra_mtl_filter_chain_t *chain, const char *param_name, float *out) {
return NULL;
}
@ -483,7 +481,7 @@ libra_error_t __librashader__noop_mtl_filter_chain_set_active_pass_count(
}
libra_error_t __librashader__noop_mtl_filter_chain_get_active_pass_count(
libra_mtl_filter_chain_t *chain, uint32_t *out) {
const libra_mtl_filter_chain_t *chain, uint32_t *out) {
return NULL;
}
#endif
@ -573,6 +571,8 @@ typedef struct libra_instance_t {
/// - GLCore
/// - Direct3D11
/// - Direct3D12
/// - Metal
/// - Direct3D9 (HLSL)
///
/// This will also set the appropriate video driver extensions.
///
@ -799,17 +799,6 @@ typedef struct libra_instance_t {
PFN_libra_error_free_string error_free_string;
#if defined(LIBRA_RUNTIME_OPENGL)
/// Initialize the OpenGL Context for librashader.
///
/// ## Safety
/// Attempting to create a filter chain will fail if the context is not
/// initialized.
///
/// Reinitializing the OpenGL context with a different loader immediately
/// invalidates previous filter chain objects, and drawing with them causes
/// immediate undefined behaviour.
PFN_libra_gl_init_context gl_init_context;
/// Create the filter chain given the shader preset.
///
/// The shader preset is immediately invalidated and must be recreated after
@ -1334,7 +1323,7 @@ typedef struct libra_instance_t {
/// struct.
PFN_libra_mtl_filter_chain_frame mtl_filter_chain_frame;
/// Free a D3D11 filter chain.
/// Free a Metal filter chain.
///
/// The resulting value in `chain` then becomes null.
/// ## Safety
@ -1445,7 +1434,6 @@ libra_instance_t __librashader_make_null_instance(void) {
instance.error_free_string = __librashader__noop_error_free_string;
#if defined(LIBRA_RUNTIME_OPENGL)
instance.gl_init_context = __librashader__noop_gl_init_context;
instance.gl_filter_chain_create =
__librashader__noop_gl_filter_chain_create;
instance.gl_filter_chain_frame = __librashader__noop_gl_filter_chain_frame;
@ -1620,7 +1608,6 @@ libra_instance_t librashader_load_instance(void) {
_LIBRASHADER_ASSIGN(librashader, instance, error_free_string);
#if defined(LIBRA_RUNTIME_OPENGL)
_LIBRASHADER_ASSIGN(librashader, instance, gl_init_context);
_LIBRASHADER_ASSIGN(librashader, instance, gl_filter_chain_create);
_LIBRASHADER_ASSIGN(librashader, instance, gl_filter_chain_frame);
_LIBRASHADER_ASSIGN(librashader, instance, gl_filter_chain_free);

View file

@ -6,9 +6,9 @@ publish = false
[dependencies]
cbindgen = "0.26.0"
clap = { version = "=4.1.0", features = ["derive"] }
cbindgen = "0.27.0"
clap = { workspace = true }
carlog = "0.1.0"
[package.metadata.release]
release = false

View file

@ -1,3 +1,4 @@
use carlog::*;
use clap::Parser;
use std::fs::File;
use std::io::{BufWriter, Write};
@ -12,6 +13,8 @@ struct Args {
profile: String,
#[arg(long, global = true)]
target: Option<String>,
#[arg(long, default_value_t = false, global = true)]
stable: bool,
#[arg(last = true)]
cargoflags: Vec<String>,
}
@ -27,9 +30,11 @@ pub fn main() -> ExitCode {
let profile = args.profile;
let crate_dir = Path::new("librashader-capi");
println!("Building librashader C API...");
carlog_info!("Building", "librashader C API");
let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
let mut cmd = Command::new(&cargo);
let mut cmd = Command::new("cargo");
cmd.arg("build");
cmd.args(["--package", "librashader-capi"]);
cmd.arg(format!(
@ -46,11 +51,22 @@ pub fn main() -> ExitCode {
cmd.arg(format!("--target={}", &target));
}
if args.stable {
carlog_warning!("building librashader with stable Rust compatibility");
carlog_warning!("C headers will not be generated");
cmd.args(["--features", "stable"]);
}
if !args.cargoflags.is_empty() {
cmd.args(args.cargoflags);
}
let status = cmd.status().expect("Failed to build librashader-capi");
let Ok(status) = cmd.status().inspect_err(|err| {
carlog_error!("failed to build librashader-capi");
carlog_error!(format!("{err}"));
}) else {
return ExitCode::FAILURE;
};
if !status.success() {
return ExitCode::from(status.code().unwrap_or(1) as u8);
}
@ -60,39 +76,76 @@ pub fn main() -> ExitCode {
output_dir = PathBuf::from(format!("target/{}/{}", target, profile));
}
let output_dir = output_dir
.canonicalize()
.expect("Could not find output directory.");
let Ok(output_dir) = output_dir.canonicalize() else {
carlog_error!("could not find output directory");
println!("help: are you running the build script from the repository root?");
return ExitCode::FAILURE;
};
println!("Generating C headers...");
if args.stable {
carlog_warning!("generating C headers is not supported when building for stable Rust");
} else {
carlog_info!("Generating", "librashader C API headers");
// Create headers.
let mut buf = BufWriter::new(Vec::new());
cbindgen::generate(crate_dir)
.expect("Unable to generate bindings")
.write(&mut buf);
// Create headers.
let mut buf = BufWriter::new(Vec::new());
let Ok(bindings) = cbindgen::generate(crate_dir).inspect_err(|err| {
carlog_error!("unable to generate C API headers");
carlog_error!(format!("{err}"));
}) else {
return ExitCode::FAILURE;
};
let bytes = buf.into_inner().expect("Unable to extract bytes");
let string = String::from_utf8(bytes).expect("Unable to create string");
File::create(output_dir.join("librashader.h"))
.expect("Unable to open file")
.write_all(string.as_bytes())
.expect("Unable to write bindings.");
bindings.write(&mut buf);
let bytes = buf.into_inner().expect("Unable to extract bytes");
let string = String::from_utf8(bytes).expect("Unable to create string");
println!("Moving artifacts...");
let Ok(mut file) = File::create(output_dir.join("librashader.h")).inspect_err(|err| {
carlog_error!("unable to open librashader.h");
carlog_error!(format!("{err}"));
}) else {
return ExitCode::FAILURE;
};
let Ok(_) = file.write_all(string.as_bytes()).inspect_err(|err| {
carlog_error!("unable to write to librashader.h");
carlog_error!(format!("{err}"));
}) else {
return ExitCode::FAILURE;
};
}
carlog_info!("Moving", "built artifacts");
if cfg!(target_os = "macos") {
let artifacts = &["liblibrashader_capi.dylib", "liblibrashader_capi.a"];
for artifact in artifacts {
let ext = artifact.strip_prefix("lib").unwrap();
let ext = ext.replace("_capi", "");
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap();
let Ok(_) =
fs::rename(output_dir.join(artifact), output_dir.join(&ext)).inspect_err(|err| {
carlog_error!(format!("Unable to rename {artifact} to {}", &ext));
carlog_error!(format!("{err}"));
})
else {
return ExitCode::FAILURE;
};
carlog_ok!("Renamed", format!("{artifact} to {}", &ext));
}
} else if cfg!(target_family = "unix") {
let artifacts = &["liblibrashader_capi.so", "liblibrashader_capi.a"];
for artifact in artifacts {
let ext = artifact.strip_prefix("lib").unwrap();
let ext = ext.replace("_capi", "");
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap();
let Ok(_) =
fs::rename(output_dir.join(artifact), output_dir.join(&ext)).inspect_err(|err| {
carlog_error!(format!("Unable to rename {artifact} to {}", &ext));
carlog_error!(format!("{err}"));
})
else {
return ExitCode::FAILURE;
};
carlog_ok!("Renamed", format!("{artifact} to {}", &ext));
}
}
@ -106,19 +159,26 @@ pub fn main() -> ExitCode {
];
for artifact in artifacts {
let ext = artifact.replace("_capi", "");
println!("Renaming {artifact} to {ext}");
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap();
let Ok(_) =
fs::rename(output_dir.join(artifact), output_dir.join(&ext)).inspect_err(|err| {
carlog_error!(format!("Unable to rename {artifact} to {}", &ext));
carlog_error!(format!("{err}"));
})
else {
return ExitCode::FAILURE;
};
carlog_ok!("Renamed", format!("{artifact} to {}", &ext));
}
if output_dir.join("librashader_capi.pdb").exists() {
println!("Renaming librashader_capi.pdb to librashader.pdb");
fs::rename(
output_dir.join("librashader_capi.pdb"),
output_dir.join("librashader.pdb"),
)
.unwrap();
carlog_ok!("Renamed", "librashader_capi.pdb to librashader.pdb");
}
}
return ExitCode::SUCCESS;
ExitCode::SUCCESS
}

View file

@ -2,7 +2,7 @@
name = "librashader-cache"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.3.0"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,16 +12,20 @@ description = "RetroArch shaders for all."
[dependencies]
serde = { version = "1.0" }
librashader-reflect = { path = "../librashader-reflect", version = "0.3.0", features = ["serialize"] }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.3.0" }
librashader-reflect = { path = "../librashader-reflect", version = "0.5.1", features = ["serde"] }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.5.1" }
platform-dirs = "0.3.0"
blake3 = { version = "1.3.3" }
blake3 = { version = "1.5.4" }
thiserror = "1.0.38"
bincode = { version = "2.0.0-rc.2", features = ["serde"] }
persy = "1.4.7"
bytemuck = "1.13.0"
[target.x86_64-win7-windows-msvc.dependencies.blake3]
version = "1.5.4"
features = ["pure"]
[target.'cfg(windows)'.dependencies.windows]
workspace = true
features = [

View file

@ -3,7 +3,7 @@ name = "librashader-capi"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.3.0"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -17,7 +17,7 @@ crate-type = [ "cdylib", "staticlib" ]
[features]
default = ["runtime-all" ]
runtime-all = ["runtime-opengl", "runtime-d3d9", "runtime-d3d11", "runtime-d3d12", "runtime-vulkan", "runtime-metal"]
runtime-opengl = ["gl", "librashader/runtime-gl"]
runtime-opengl = ["glow", "librashader/runtime-gl"]
runtime-d3d11 = ["windows", "librashader/runtime-d3d11", "windows/Win32_Graphics_Direct3D11"]
runtime-d3d12 = ["windows", "librashader/runtime-d3d12", "windows/Win32_Graphics_Direct3D12"]
runtime-d3d9 = ["windows", "librashader/runtime-d3d9", "windows/Win32_Graphics_Direct3D9"]
@ -26,6 +26,8 @@ runtime-vulkan = ["ash", "librashader/runtime-vk"]
runtime-metal = ["__cbindgen_internal_objc", "librashader/runtime-metal"]
reflect-unstable = []
stable = ["librashader/stable"]
docsrs = []
__cbindgen_internal = ["runtime-all"]
@ -36,15 +38,16 @@ __cbindgen_internal_objc = ["objc2-metal", "objc2"]
[dependencies]
thiserror = "1.0.37"
paste = "1.0.9"
gl = { version = "0.14.0", optional = true }
rustc-hash = "2.0.0"
ash = { version = "0.38", optional = true }
spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1" }
sptr = "0.3.2"
glow = { workspace = true, optional = true }
ash = { workspace = true, optional = true }
[dependencies.librashader]
path = "../librashader"
version = "0.3.0"
version = "0.5.1"
default-features = false
features = ["reflect", "presets", "preprocess"]
@ -64,4 +67,4 @@ targets = [ "x86_64-pc-windows-msvc",
"aarch64-apple-ios",
"i686-pc-windows-msvc",
"i686-unknown-linux-gnu", ]
features = ["librashader/docsrs"]
features = ["docsrs", "librashader/docsrs"]

View file

@ -173,6 +173,7 @@ include = [
exclude = [
"Option_ID3D11DeviceContext",
"Option_PFN_vkGetInstanceProcAddr",
"PMTLCommandQueue",
"PMTLCommandBuffer",
"PMTLTexture"
@ -198,6 +199,7 @@ exclude = [
"CommandBuffer" = "VkCommandBuffer"
"Format" = "VkFormat"
"Image" = "VkImage"
"Queue" = "VkQueue"
# hack to get proper pointer indirection for COM pointers
# we don't need one for ID3D11DeviceContext.
@ -214,6 +216,9 @@ exclude = [
# hack to force cbindgen to not generate option type for nullable ID3D11DeviceContext.
"Option_ID3D11DeviceContext" = "ID3D11DeviceContext *"
# hack to force cbindgen to not generate option type for nullable PFN_vkGetInstanceProcAddr.
"Option_PFN_vkGetInstanceProcAddr" = "PFN_vkGetInstanceProcAddr"
# hack to get proper pointer indirection for COM pointers
"ID3D12Device" = "ID3D12Device *"
"ID3D12Resource" = "ID3D12Resource *"

View file

@ -18,7 +18,9 @@ pub type libra_error_t = Option<NonNull<LibrashaderError>>;
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum LIBRA_PRESET_CTX_ORIENTATION {
/// Context parameter for vertical orientation.
Vertical = 0,
/// Context parameter for horizontal orientation.
Horizontal,
}
impl From<LIBRA_PRESET_CTX_ORIENTATION> for Orientation {
@ -30,16 +32,24 @@ impl From<LIBRA_PRESET_CTX_ORIENTATION> for Orientation {
}
}
// An enum representing graphics runtimes (video drivers) for use in preset contexts.
/// An enum representing graphics runtimes (video drivers) for use in preset contexts.
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum LIBRA_PRESET_CTX_RUNTIME {
/// No runtime.
None = 0,
/// OpenGL 3.3+
GlCore,
/// Vulkan
Vulkan,
/// Direct3D 11
D3D11,
/// Direct3D 12
D3D12,
/// Metal
Metal,
/// Direct3D 9
D3D9_HLSL,
}
impl From<LIBRA_PRESET_CTX_RUNTIME> for VideoDriver {
@ -51,6 +61,7 @@ impl From<LIBRA_PRESET_CTX_RUNTIME> for VideoDriver {
LIBRA_PRESET_CTX_RUNTIME::D3D11 => VideoDriver::Direct3D11,
LIBRA_PRESET_CTX_RUNTIME::D3D12 => VideoDriver::Direct3D12,
LIBRA_PRESET_CTX_RUNTIME::Metal => VideoDriver::Metal,
LIBRA_PRESET_CTX_RUNTIME::D3D9_HLSL => VideoDriver::Direct3D9Hlsl,
}
}
}
@ -60,7 +71,7 @@ use librashader::runtime::gl::FilterChain as FilterChainGL;
/// A handle to a OpenGL filter chain.
#[cfg(feature = "runtime-opengl")]
#[doc(cfg(feature = "runtime-opengl"))]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-opengl")))]
pub type libra_gl_filter_chain_t = Option<NonNull<FilterChainGL>>;
/// A handle to a Direct3D 11 filter chain.
@ -71,7 +82,10 @@ pub type libra_gl_filter_chain_t = Option<NonNull<FilterChainGL>>;
use librashader::runtime::d3d11::FilterChain as FilterChainD3D11;
/// A handle to a Direct3D 11 filter chain.
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))
)]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d11")
@ -98,7 +112,10 @@ pub type libra_d3d12_filter_chain_t = Option<NonNull<FilterChainD3D12>>;
use librashader::runtime::d3d9::FilterChain as FilterChainD3D9;
/// A handle to a Direct3D 11 filter chain.
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))
)]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d9")
@ -109,12 +126,17 @@ pub type libra_d3d9_filter_chain_t = Option<NonNull<FilterChainD3D9>>;
use librashader::runtime::vk::FilterChain as FilterChainVulkan;
/// A handle to a Vulkan filter chain.
#[cfg(feature = "runtime-vulkan")]
#[doc(cfg(feature = "runtime-vulkan"))]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-vulkan")))]
pub type libra_vk_filter_chain_t = Option<NonNull<FilterChainVulkan>>;
#[cfg(all(target_os = "macos", feature = "runtime-metal"))]
use librashader::runtime::mtl::FilterChain as FilterChainMetal;
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
/// A handle to a Vulkan filter chain.
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))
)]
#[cfg(any(
feature = "__cbindgen_internal",
all(
@ -125,16 +147,18 @@ use librashader::runtime::mtl::FilterChain as FilterChainMetal;
))]
pub type libra_mtl_filter_chain_t = Option<NonNull<FilterChainMetal>>;
/// Defines the output viewport for a rendered frame.
/// Defines the output origin for a rendered frame.
#[repr(C)]
pub struct libra_viewport_t {
/// The x offset in the viewport framebuffer to begin rendering from.
pub x: f32,
/// The y offset in the viewport framebuffer to begin rendering from.
pub y: f32,
/// The width of the viewport framebuffer.
/// The width extent of the viewport framebuffer to end rendering, relative to
/// the origin specified by x.
pub width: u32,
/// The height of the viewport framebuffer.
/// The height extent of the viewport framebuffer to end rendering, relative to
/// the origin specified by y.
pub height: u32,
}
@ -146,29 +170,85 @@ where
}
macro_rules! config_set_field {
($options:ident.$field:ident <- $ptr:ident) => {
(@POINTER $options:ident.$field:ident <- $ptr:ident) => {
$options.$field = unsafe { ::std::ptr::addr_of!((*$ptr).$field).read() };
};
(@POINTER @NEGATIVE $options:ident.$field:ident <- $ptr:ident) => {
$options.$field = unsafe { !::std::ptr::addr_of!((*$ptr).$field).read() };
};
(@LITERAL $options:ident.$field:ident <- $value:literal) => {
$options.$field = $value;
};
}
macro_rules! config_version_set {
($version:literal => [$($field:ident),+ $(,)?] ($options:ident <- $ptr:ident)) => {
let version = unsafe { ::std::ptr::addr_of!((*$ptr).version).read() };
// "optimized" version for normal behaviour
(@ROOT $realver:ident $version:literal => [$($field:ident),+ $(,)?] ($options:ident <- $ptr:ident)) => {
#[allow(unused_comparisons)]
if version >= $version {
$($crate::ctypes::config_set_field!($options.$field <- $ptr);)+
if $realver >= $version {
$($crate::ctypes::config_set_field!(@POINTER $options.$field <- $ptr);)+
}
}
};
// Repeater
(@ROOT $realver:ident $version:literal => [$($field:tt),+ $(,)?] ($options:ident <- $ptr:ident)) => {
$(crate::ctypes::config_version_set!(@SINGLE $realver $version => [$field] ($options <- $ptr));)+
};
// Allow overriding default value with a literal for older versions
(@SINGLE $realver:ident $version:literal => [($field:ident: $value:literal)] ($options:ident <- $ptr:ident)) => {
#[allow(unused_comparisons)]
if $realver >= $version {
$crate::ctypes::config_set_field!(@LITERAL $options.$field <- $value);
}
};
// Allow negation of prior variables that is version dependent.
(@SINGLE $realver:ident $version:literal => [(!$field:ident)] ($options:ident <- $ptr:ident)) => {
#[allow(unused_comparisons)]
if $realver >= $version {
$crate::ctypes::config_set_field!(@POINTER @NEGATIVE $options.$field <- $ptr);
}
};
(@SINGLE $realver:ident $version:literal => [$field:ident] ($options:ident <- $ptr:ident)) => {
#[allow(unused_comparisons)]
if $realver >= $version {
$crate::ctypes::config_set_field!(@POINTER $options.$field <- $ptr);
}
};
}
/// Macro to declare a configuration struct, with options to change behaviour based on
/// API version.
///
/// For example following declaration does the following
///
/// * Declare `frames_in_flight`, `use_dynamic_rendering` for API version 0, with the following forward compatibility statements
/// * Inverts the behaviour of `use_dynamic_rendering` compared to API version 1.
/// * `disable_cache` is defaulted to `true` for API version 0, regardless of `Default::default`
/// but is not declared for API 0.
/// * Declare `use_dynamic_rendering` with normal behaviour, and `disable_cache` for API version 1.
/// * All fields that are undeclared inherit `Default::default`
///
/// ```rust
/// config_struct! {
/// impl FilterChainOptions => filter_chain_vk_opt_t {
/// 0 => [frames_in_flight, (!use_dynamic_rendering), (disable_cache: true)];
/// 1 => [use_dynamic_rendering, disable_cache];
/// }
/// }
/// ```
macro_rules! config_struct {
(impl $rust:ty => $capi:ty {$($version:literal => [$($field:ident),+ $(,)?]);+ $(;)?}) => {
(impl $rust:ty => $capi:ty {$($version:literal => [$($field:tt),+]);+ $(;)?}) => {
impl $crate::ctypes::FromUninit<$rust> for $capi {
fn from_uninit(value: ::std::mem::MaybeUninit<Self>) -> $rust {
let ptr = value.as_ptr();
let version = unsafe { ::std::ptr::addr_of!((*ptr).version).read() };
let mut options = <$rust>::default();
$(
$crate::ctypes::config_version_set!($version => [$($field),+] (options <- ptr));
$crate::ctypes::config_version_set!(@ROOT version $version => [$($field),+] (options <- ptr));
)+
options
}

View file

@ -9,58 +9,116 @@ use thiserror::Error;
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum LibrashaderError {
/// An unknown error or panic occurred.
#[error("There was an unknown error.")]
UnknownError(Box<dyn Any + Send + 'static>),
/// An invalid parameter (likely null), was passed.
#[error("The parameter was null or invalid.")]
InvalidParameter(&'static str),
/// The string provided was not valid UTF-8.
#[error("The provided string was not valid UTF8.")]
InvalidString(#[from] std::str::Utf8Error),
/// An error occurred in the preset parser.
#[error("There was an error parsing the preset.")]
PresetError(#[from] librashader::presets::ParsePresetError),
/// An error occurred in the shader preprocessor.
#[error("There was an error preprocessing the shader source.")]
PreprocessError(#[from] librashader::preprocess::PreprocessError),
/// An error occurred in the shader compiler.
#[error("There was an error compiling the shader source.")]
ShaderCompileError(#[from] librashader::reflect::ShaderCompileError),
/// An error occrred when validating and reflecting the shader.
#[error("There was an error reflecting the shader source.")]
ShaderReflectError(#[from] librashader::reflect::ShaderReflectError),
/// An invalid shader parameter name was provided.
#[error("The provided parameter name was invalid.")]
UnknownShaderParameter(*const c_char),
/// An error occurred with the OpenGL filter chain.
#[cfg(feature = "runtime-opengl")]
#[doc(cfg(feature = "runtime-opengl"))]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-opengl")))]
#[error("There was an error in the OpenGL filter chain.")]
OpenGlFilterError(#[from] librashader::runtime::gl::error::FilterChainError),
/// An error occurred with the Direct3D 11 filter chain.
#[cfg(all(target_os = "windows", feature = "runtime-d3d11"))]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))
)]
#[error("There was an error in the D3D11 filter chain.")]
D3D11FilterError(#[from] librashader::runtime::d3d11::error::FilterChainError),
/// An error occurred with the Direct3D 12 filter chain.
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))
)]
#[error("There was an error in the D3D12 filter chain.")]
D3D12FilterError(#[from] librashader::runtime::d3d12::error::FilterChainError),
/// An error occurred with the Direct3D 9 filter chain.
#[cfg(all(target_os = "windows", feature = "runtime-d3d9"))]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))
)]
#[error("There was an error in the D3D9 filter chain.")]
D3D9FilterError(#[from] librashader::runtime::d3d9::error::FilterChainError),
/// An error occurred with the Vulkan filter chain.
#[cfg(feature = "runtime-vulkan")]
#[doc(cfg(feature = "runtime-vulkan"))]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-vulkan")))]
#[error("There was an error in the Vulkan filter chain.")]
VulkanFilterError(#[from] librashader::runtime::vk::error::FilterChainError),
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
/// An error occurred with the Metal filter chain.
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))
)]
#[cfg(all(target_vendor = "apple", feature = "runtime-metal"))]
#[error("There was an error in the D3D12 filter chain.")]
#[error("There was an error in the Metal filter chain.")]
MetalFilterError(#[from] librashader::runtime::mtl::error::FilterChainError),
/// This error is unreachable.
#[error("This error is not reachable")]
Infallible(#[from] std::convert::Infallible),
}
/// Error codes for librashader error types.
#[repr(i32)]
pub enum LIBRA_ERRNO {
/// Error code for an unknown error.
UNKNOWN_ERROR = 0,
/// Error code for an invalid parameter.
INVALID_PARAMETER = 1,
/// Error code for an invalid (non-UTF8) string.
INVALID_STRING = 2,
/// Error code for a preset parser error.
PRESET_ERROR = 3,
/// Error code for a preprocessor error.
PREPROCESS_ERROR = 4,
/// Error code for a shader parameter error.
SHADER_PARAMETER_ERROR = 5,
/// Error code for a reflection error.
REFLECT_ERROR = 6,
/// Error code for a runtime error.
RUNTIME_ERROR = 7,
}
@ -199,6 +257,7 @@ impl LibrashaderError {
LibrashaderError::VulkanFilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
#[cfg(all(target_vendor = "apple", feature = "runtime-metal"))]
LibrashaderError::MetalFilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
LibrashaderError::Infallible(_) => LIBRA_ERRNO::UNKNOWN_ERROR,
}
}
pub(crate) const fn ok() -> libra_error_t {
@ -211,12 +270,12 @@ impl LibrashaderError {
}
macro_rules! assert_non_null {
($value:ident) => {
(@EXPORT $value:ident) => {
if $value.is_null() || !$crate::ffi::ptr_is_aligned($value) {
return $crate::error::LibrashaderError::InvalidParameter(stringify!($value)).export();
}
};
(noexport $value:ident) => {
($value:ident) => {
if $value.is_null() || !$crate::ffi::ptr_is_aligned($value) {
return Err($crate::error::LibrashaderError::InvalidParameter(
stringify!($value),
@ -228,14 +287,18 @@ macro_rules! assert_non_null {
macro_rules! assert_some_ptr {
($value:ident) => {
if $value.is_none() {
return $crate::error::LibrashaderError::InvalidParameter(stringify!($value)).export();
return Err($crate::error::LibrashaderError::InvalidParameter(
stringify!($value),
));
}
let $value = unsafe { $value.as_ref().unwrap_unchecked().as_ref() };
};
(mut $value:ident) => {
if $value.is_none() {
return $crate::error::LibrashaderError::InvalidParameter(stringify!($value)).export();
return Err($crate::error::LibrashaderError::InvalidParameter(
stringify!($value),
));
}
let $value = unsafe { $value.as_mut().unwrap_unchecked().as_mut() };

View file

@ -1,9 +1,15 @@
macro_rules! wrap_ok {
($e:expr) => {
::core::iter::empty().try_fold($e, |_, __x: ::core::convert::Infallible| match __x {})
};
}
macro_rules! ffi_body {
(nopanic $body:block) => {
{
let result: Result<(), $crate::error::LibrashaderError> = try {
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
$body
};
}))();
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
@ -22,13 +28,13 @@ macro_rules! ffi_body {
};
(nopanic |$($ref_capture:ident),*|; mut |$($mut_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!($ref_capture);)*
$($crate::error::assert_non_null!(@EXPORT $ref_capture);)*
$(let $ref_capture = unsafe { &*$ref_capture };)*
$($crate::error::assert_non_null!($mut_capture);)*
$($crate::error::assert_non_null!(@EXPORT $mut_capture);)*
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = try {
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
$body
};
}))();
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
@ -47,11 +53,11 @@ macro_rules! ffi_body {
};
(nopanic mut |$($mut_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!($mut_capture);)*
$($crate::error::assert_non_null!(@EXPORT $mut_capture);)*
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = try {
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
$body
};
}))();
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
@ -70,11 +76,11 @@ macro_rules! ffi_body {
};
(nopanic |$($ref_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!($ref_capture);)*
$($crate::error::assert_non_null!(@EXPORT $ref_capture);)*
$(let $ref_capture = unsafe { &*$ref_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = try {
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
$body
};
}))();
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
@ -231,16 +237,16 @@ pub unsafe fn boxed_slice_from_raw_parts<T>(ptr: *mut T, len: usize) -> Box<[T]>
unsafe { Box::from_raw(std::slice::from_raw_parts_mut(ptr, len)) }
}
#[allow(unstable_name_collisions)]
pub fn ptr_is_aligned<T: Sized>(ptr: *const T) -> bool {
use sptr::Strict;
let align = std::mem::align_of::<T>();
if !align.is_power_of_two() {
panic!("is_aligned_to: align is not a power-of-two");
}
ptr.addr() & (align - 1) == 0
sptr::Strict::addr(ptr) & (align - 1) == 0
}
pub(crate) use extern_fn;
pub(crate) use ffi_body;
pub(crate) use wrap_ok;
use std::mem::ManuallyDrop;

View file

@ -1,4 +1,4 @@
#![feature(doc_cfg)]
#![forbid(missing_docs)]
//! The C API for [librashader](https://docs.rs/librashader/).
//!
//! The librashader C API is designed to be loaded dynamically via `librashader_ld.h`, but static usage is also
@ -60,11 +60,13 @@
//! to the same context as the drawing thread, and in Direct3D 11, where filter chain creation is unsafe
//! if the `ID3D11Device` was created with `D3D11_CREATE_DEVICE_SINGLETHREADED`. Metal is entirely thread unsafe.
//!
//! Setting and retrieving filter parameters from any thread, regardless of the lack of other thread safety-guarantees
//! of the runtime, is always thread safe.
//!
//! You must ensure that only thread has access to a created filter pass **before** you call `*_frame`. `*_frame` may only be
//! called from one thread at a time.
#![cfg_attr(feature = "docsrs", feature(doc_cfg))]
#![allow(non_camel_case_types)]
#![feature(try_blocks)]
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(deprecated)]

View file

@ -14,11 +14,10 @@ const _: () = crate::assert_thread_safe::<ShaderPreset>();
pub struct libra_preset_param_list_t {
/// A pointer to the parameter
pub parameters: *const libra_preset_param_t,
/// The number of parameters in the list.
/// The number of parameters in the list. This field
/// is readonly, and changing it will lead to undefined
/// behaviour on free.
pub length: u64,
/// For internal use only.
/// Changing this causes immediate undefined behaviour on freeing this parameter list.
pub _internal_alloc: u64,
}
/// A preset parameter.
@ -158,7 +157,7 @@ extern_fn! {
/// - `name` must be null or a valid and aligned pointer to a string.
/// - `value` may be a pointer to a uninitialized `float`.
fn libra_preset_get_param(
preset: *mut libra_shader_preset_t,
preset: *const libra_shader_preset_t,
name: *const c_char,
value: *mut MaybeUninit<f32>
) |name, preset| {
@ -196,7 +195,7 @@ extern_fn! {
/// - It is safe to call `libra_preset_get_runtime_params` multiple times, however
/// the output struct must only be freed once per call.
fn libra_preset_get_runtime_params(
preset: *mut libra_shader_preset_t,
preset: *const libra_shader_preset_t,
out: *mut MaybeUninit<libra_preset_param_list_t>
) |preset| {
assert_some_ptr!(preset);
@ -205,7 +204,7 @@ extern_fn! {
let iter = librashader::presets::get_parameter_meta(preset)?;
let mut values = Vec::new();
for param in iter {
let name = CString::new(param.id)
let name = CString::new(param.id.to_string())
.map_err(|err| LibrashaderError::UnknownError(Box::new(err)))?;
let description = CString::new(param.description)
.map_err(|err| LibrashaderError::UnknownError(Box::new(err)))?;
@ -226,7 +225,6 @@ extern_fn! {
out.write(MaybeUninit::new(libra_preset_param_list_t {
parameters: parts,
length: len as u64,
_internal_alloc: 0,
}));
}
}

View file

@ -1,6 +1,6 @@
use crate::error;
use librashader::presets::{ShaderPassConfig, ShaderPreset};
use librashader::presets::{PassConfig, ShaderPreset};
use librashader::reflect::semantics::ShaderSemantics;
use librashader::reflect::targets::SPIRV;
use librashader::reflect::{CompileShader, ReflectShader, ShaderCompilerOutput, ShaderReflection};
@ -21,7 +21,7 @@ pub(crate) struct LookupTexture {
pub(crate) struct PassReflection {
reflection: ShaderReflection,
config: ShaderPassConfig,
config: PassConfig,
spirv: ShaderCompilerOutput<Vec<u32>>,
}
pub(crate) struct FilterReflection {
@ -35,7 +35,7 @@ impl FilterReflection {
preset: ShaderPreset,
direction: UVDirection,
) -> Result<FilterReflection, error::LibrashaderError> {
let (passes, textures) = (preset.shaders, preset.textures);
let (passes, textures) = (preset.passes, preset.textures);
let (passes, semantics) = librashader::reflect::helper::compile_preset_passes::<
Glslang,

View file

@ -15,18 +15,8 @@ use windows::Win32::Graphics::Direct3D11::{
};
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::{FilterChainParameters, Viewport};
/// Direct3D 11 parameters for the source image.
#[repr(C)]
pub struct libra_source_image_d3d11_t {
/// A shader resource view into the source image
pub handle: ManuallyDrop<ID3D11ShaderResourceView>,
/// This is currently ignored.
pub width: u32,
/// This is currently ignored.
pub height: u32,
}
use librashader::runtime::d3d11::error::FilterChainError;
use librashader::runtime::{FilterChainParameters, Size, Viewport};
/// Options for Direct3D 11 filter chain creation.
#[repr(C)]
@ -59,7 +49,7 @@ pub struct frame_d3d11_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
@ -189,10 +179,26 @@ const _: () = assert!(
extern_fn! {
/// Draw a frame with the given parameters for the given filter chain.
///
/// If `device_context` is null, then commands are recorded onto the immediate context. Otherwise,
/// it will record commands onto the provided context. If the context is deferred, librashader
/// will not finalize command lists. The context must otherwise be associated with the `ID3D11Device`
// the filter chain was created with.
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `device_context` is the ID3D11DeviceContext used to record draw commands to.
/// If `device_context` is null, then commands are recorded onto the immediate context. Otherwise,
/// it will record commands onto the provided context. If the context is deferred, librashader
/// will not finalize command lists. The context must otherwise be associated with the `ID3D11Device`
/// the filter chain was created with.
///
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a pointer to a `ID3D11ShaderResourceView` that will serve as the source image for the frame.
/// - `out` is a pointer to a `ID3D11RenderTargetView` that will serve as the render target for the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
@ -214,9 +220,9 @@ extern_fn! {
// so ManuallyDrop<Option<ID3D11DeviceContext>> doesn't generate correct bindings.
device_context: Option<ManuallyDrop<ID3D11DeviceContext>>,
frame_count: usize,
image: libra_source_image_d3d11_t,
viewport: libra_viewport_t,
image: ManuallyDrop<ID3D11ShaderResourceView>,
out: ManuallyDrop<ID3D11RenderTargetView>,
viewport: *const libra_viewport_t,
mvp: *const f32,
options: *const MaybeUninit<frame_d3d11_opt_t>
) mut |chain| {
@ -234,17 +240,27 @@ extern_fn! {
Some(unsafe { options.read() })
};
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output: ManuallyDrop::into_inner(out.clone()),
mvp,
let viewport = if viewport.is_null() {
Viewport::new_render_target_sized_origin(out.deref(), mvp)
.map_err(|e| LibrashaderError::D3D11FilterError(FilterChainError::Direct3DError(e)))?
} else {
let viewport = unsafe { viewport.read() };
Viewport {
x: viewport.x,
y: viewport.y,
output: out.deref(),
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
};
let options = options.map(FromUninit::from_uninit);
unsafe {
chain.frame(device_context.as_deref(), image.handle.deref(), &viewport, frame_count, options.as_ref())?;
chain.frame(device_context.as_deref(), image.deref(), &viewport, frame_count, options.as_ref())?;
}
}
}
@ -260,15 +276,15 @@ extern_fn! {
chain: *mut libra_d3d11_filter_chain_t,
param_name: *const c_char,
value: f32
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
assert_non_null!(param_name);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
if chain.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
}
}
}
@ -282,18 +298,18 @@ extern_fn! {
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d11_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_d3d11_filter_chain_get_param(
chain: *mut libra_d3d11_filter_chain_t,
chain: *const libra_d3d11_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
assert_non_null!(param_name);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
let Some(value) = chain.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
};
out.write(MaybeUninit::new(value));
@ -309,9 +325,9 @@ extern_fn! {
fn libra_d3d11_filter_chain_set_active_pass_count(
chain: *mut libra_d3d11_filter_chain_t,
value: u32
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
}
}
@ -321,12 +337,12 @@ extern_fn! {
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d11_filter_chain_t`.
fn libra_d3d11_filter_chain_get_active_pass_count(
chain: *mut libra_d3d11_filter_chain_t,
chain: *const libra_d3d11_filter_chain_t,
out: *mut MaybeUninit<u32>
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
unsafe {
let value = chain.get_enabled_pass_count();
let value = chain.parameters().passes_enabled();
out.write(MaybeUninit::new(value as u32))
}
}

View file

@ -19,19 +19,47 @@ use librashader::runtime::d3d12::{
};
use librashader::runtime::{FilterChainParameters, Size, Viewport};
/// Tagged union for a Direct3D 12 image
#[repr(C)]
pub struct libra_image_d3d12_t {
/// The type of the image.
pub image_type: LIBRA_D3D12_IMAGE_TYPE,
/// The handle to the image.
pub handle: libra_image_d3d12_handle_t,
}
/// A handle to a Direct3D 12 image.
///
/// This must be either a pointer to a `ID3D12Resource`,
/// or a valid source or output image type.
#[repr(C)]
pub union libra_image_d3d12_handle_t {
/// A pointer to an `ID3D12Resource`, with descriptors managed by the filter chain.
pub resource: ManuallyDrop<ID3D12Resource>,
/// A source image with externally managed descriptors.
pub source: ManuallyDrop<libra_source_image_d3d12_t>,
/// An output image with externally managed descriptors.
pub output: ManuallyDrop<libra_output_image_d3d12_t>,
}
/// The type of image passed to the image.
#[repr(i32)]
pub enum LIBRA_D3D12_IMAGE_TYPE {
/// The image handle is a pointer to a `ID3D12Resource`.
RESOURCE = 0,
/// The image handle is a `libra_source_image_d3d12_t`
SOURCE_IMAGE = 1,
/// The image handle is a `libra_output_image_d3d12_t`
OUTPUT_IMAGE = 2,
}
/// Direct3D 12 parameters for the source image.
#[repr(C)]
pub struct libra_source_image_d3d12_t {
/// The resource containing the image.
pub resource: ManuallyDrop<ID3D12Resource>,
/// A CPU descriptor handle to a shader resource view of the image.
pub descriptor: D3D12_CPU_DESCRIPTOR_HANDLE,
/// This is currently ignored.
pub format: DXGI_FORMAT,
/// This is currently ignored.
pub width: u32,
/// This is currently ignored.
pub height: u32,
/// The resource containing the image.
pub resource: ManuallyDrop<ID3D12Resource>,
}
/// Direct3D 12 parameters for the output image.
@ -41,6 +69,10 @@ pub struct libra_output_image_d3d12_t {
pub descriptor: D3D12_CPU_DESCRIPTOR_HANDLE,
/// The format of the image.
pub format: DXGI_FORMAT,
/// The width of the output image.
pub width: u32,
/// The height of the output image.
pub height: u32,
}
/// Options for each Direct3D 12 shader frame.
@ -54,7 +86,7 @@ pub struct frame_d3d12_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
@ -95,19 +127,6 @@ config_struct! {
}
}
impl TryFrom<libra_source_image_d3d12_t> for D3D12InputImage {
type Error = LibrashaderError;
fn try_from(value: libra_source_image_d3d12_t) -> Result<Self, Self::Error> {
let resource = value.resource.clone();
Ok(D3D12InputImage {
resource: ManuallyDrop::into_inner(resource),
descriptor: value.descriptor,
})
}
}
extern_fn! {
/// Create the filter chain given the shader preset.
///
@ -210,13 +229,39 @@ extern_fn! {
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command list.
///
/// * The input image must be in the `D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE` resource state.
/// * The output image must be in `D3D12_RESOURCE_STATE_RENDER_TARGET` resource state.
///
/// librashader **will not** create a resource barrier for the final pass. The output image will
/// A resource barrier **will not** be created for the final pass. The output image will
/// remain in `D3D12_RESOURCE_STATE_RENDER_TARGET` after all shader passes. The caller must transition
/// the output image to the final resource state.
///
/// The refcount of any COM pointers passed into this frame will not be changed. It is the responsibility
/// of the caller to ensure any resources remain alive until the `ID3D12GraphicsCommandList` provided is
/// submitted.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `command_list` is a `ID3D12GraphicsCommandList` to record draw commands to.
/// The provided command list must be open and associated with the `ID3D12Device` this filter chain was created with.
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `libra_image_d3d12_t` with `image_type` set to `IMAGE_TYPE_SOURCE_IMAGE` or `IMAGE_TYPE_RESOURCE`,
/// with `image_handle` either a `ID3D12Resource*` or an `libra_source_image_d3d12_t` containing a CPU descriptor to a shader resource view,
/// and the image resource the view is of, which will serve as the source image for the frame.
/// The input image resource must be in the `D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE` resource state
/// or equivalent barrier layout. The image resource must have dimension `D3D12_RESOURCE_DIMENSION_TEXTURE2D`.
/// - `out` is a `libra_image_d3d12_t`, with `image_type` set to `IMAGE_TYPE_OUTPUT_IMAGE` or `IMAGE_TYPE_RESOURCE`,
/// with `image_handle` being either a `ID3D12Resource*` or an `libra_output_image_d3d12_t`, containing a CPU descriptor handle,
/// format, and size information for the render target of the frame. The output image must be in
/// `D3D12_RESOURCE_STATE_RENDER_TARGET` resource state or equivalent barrier layout.
/// The image resource must have dimension `D3D12_RESOURCE_DIMENSION_TEXTURE2D`.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
@ -224,19 +269,24 @@ extern_fn! {
/// values for the model view projection matrix.
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_d3d12_opt_t`
/// struct.
/// - `out` must be a descriptor handle to a render target view.
/// - `image.resource` must not be null.
/// - Any resource pointers contained within a `libra_image_d3d12_t` must be non-null.
/// - The `handle` field of any `libra_image_d3d12_t` must be valid for it's `image_type`.
/// - If `image_type` is `IMAGE_TYPE_RESOURCE`, then `handle` must be `ID3D12Resource *`.
/// - If `image_type` is `IMAGE_TYPE_SOURCE`, then `handle` must be `libra_source_image_d3d12_t`.
/// - If `image_type` is `IMAGE_TYPE_OUTPUT`, then `handle` must be `libra_output_image_d3d12_t`.
/// - `command_list` must be a non-null pointer to a `ID3D12GraphicsCommandList` that is open,
/// and must be associated with the `ID3D12Device` this filter chain was created with.
/// - All resource pointers contained within a `libra_image_d3d12_t` must remain valid until the `ID3D12GraphicsCommandList *`
/// provided is submitted after the call to this function.
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
/// thread at a time may call this function.
nopanic fn libra_d3d12_filter_chain_frame(
chain: *mut libra_d3d12_filter_chain_t,
command_list: ManuallyDrop<ID3D12GraphicsCommandList>,
frame_count: usize,
image: libra_source_image_d3d12_t,
viewport: libra_viewport_t,
out: libra_output_image_d3d12_t,
image: libra_image_d3d12_t,
out: libra_image_d3d12_t,
viewport: *const libra_viewport_t,
mvp: *const f32,
options: *const MaybeUninit<frame_d3d12_opt_t>
) mut |chain| {
@ -255,14 +305,65 @@ extern_fn! {
};
let options = options.map(FromUninit::from_uninit);
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output: unsafe { D3D12OutputView::new_from_raw(out.descriptor, Size::new(viewport.width, viewport.height), out.format) },
mvp,
let output = unsafe {
match out.image_type {
LIBRA_D3D12_IMAGE_TYPE::RESOURCE => {
let out = out.handle.resource;
D3D12OutputView::new_from_resource(
out,
chain,
)?
}
LIBRA_D3D12_IMAGE_TYPE::OUTPUT_IMAGE => {
let out = out.handle.output;
D3D12OutputView::new_from_raw(
out.descriptor,
Size::new(out.width, out.height),
out.format,
)
}
LIBRA_D3D12_IMAGE_TYPE::SOURCE_IMAGE => {
return Err(LibrashaderError::InvalidParameter("out"))
}
}
};
let viewport = if viewport.is_null() {
Viewport::new_render_target_sized_origin(output, mvp)?
} else {
let viewport = unsafe { viewport.read() };
Viewport {
x: viewport.x,
y: viewport.y,
output,
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
};
let image = unsafe {
match image.image_type {
LIBRA_D3D12_IMAGE_TYPE::RESOURCE => {
let image = image.handle.resource;
D3D12InputImage::Managed(image)
}
LIBRA_D3D12_IMAGE_TYPE::SOURCE_IMAGE => {
let image = ManuallyDrop::into_inner(image.handle.source);
D3D12InputImage::External {
resource: image.resource,
descriptor: image.descriptor,
}
}
LIBRA_D3D12_IMAGE_TYPE::OUTPUT_IMAGE => {
return Err(LibrashaderError::InvalidParameter("image"))
}
}
};
let image = image.try_into()?;
unsafe {
chain.frame(&command_list, image, &viewport, frame_count, options.as_ref())?;
}
@ -280,15 +381,15 @@ extern_fn! {
chain: *mut libra_d3d12_filter_chain_t,
param_name: *const c_char,
value: f32
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
assert_non_null!(param_name);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
if chain.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
}
}
}
@ -302,18 +403,18 @@ extern_fn! {
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d12_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_d3d12_filter_chain_get_param(
chain: *mut libra_d3d12_filter_chain_t,
chain: *const libra_d3d12_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
assert_non_null!(param_name);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
let Some(value) = chain.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
};
out.write(MaybeUninit::new(value));
@ -329,9 +430,9 @@ extern_fn! {
fn libra_d3d12_filter_chain_set_active_pass_count(
chain: *mut libra_d3d12_filter_chain_t,
value: u32
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
}
}
@ -341,12 +442,12 @@ extern_fn! {
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d12_filter_chain_t`.
fn libra_d3d12_filter_chain_get_active_pass_count(
chain: *mut libra_d3d12_filter_chain_t,
chain: *const libra_d3d12_filter_chain_t,
out: *mut MaybeUninit<u32>
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
unsafe {
let value = chain.get_enabled_pass_count();
let value = chain.parameters().passes_enabled();
out.write(MaybeUninit::new(value as u32))
}
}

View file

@ -7,12 +7,14 @@ use librashader::runtime::d3d9::{FilterChain, FilterChainOptions, FrameOptions};
use std::ffi::c_char;
use std::ffi::CStr;
use std::mem::{ManuallyDrop, MaybeUninit};
use std::ops::Deref;
use std::ptr::NonNull;
use std::slice;
use windows::Win32::Graphics::Direct3D9::{IDirect3DDevice9, IDirect3DSurface9, IDirect3DTexture9};
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::{FilterChainParameters, Viewport};
use librashader::runtime::d3d9::error::FilterChainError;
use librashader::runtime::{FilterChainParameters, Size, Viewport};
/// Options for Direct3D 11 filter chain creation.
#[repr(C)]
@ -45,7 +47,7 @@ pub struct frame_d3d9_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
@ -108,10 +110,24 @@ extern_fn! {
extern_fn! {
/// Draw a frame with the given parameters for the given filter chain.
///
/// ## Parameters
/// - `chain` is a handle to the filter chain.
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a pointer to a `IDirect3DTexture9` that will serve as the source image for the frame.
/// - `out` is a pointer to a `IDirect3DSurface9` that will serve as the render target for the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
/// - `viewport` may be null, or if it is not null, must be an aligned pointer to an instance of `libra_viewport_t`.
/// - `mvp` may be null, or if it is not null, must be an aligned pointer to 16 consecutive `float`
/// values for the model view projection matrix.
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_d3d9_opt_t`
@ -124,8 +140,8 @@ extern_fn! {
chain: *mut libra_d3d9_filter_chain_t,
frame_count: usize,
image: ManuallyDrop<IDirect3DTexture9>,
viewport: libra_viewport_t,
out: ManuallyDrop<IDirect3DSurface9>,
viewport: *const libra_viewport_t,
mvp: *const f32,
options: *const MaybeUninit<frame_d3d9_opt_t>
) mut |chain| {
@ -143,18 +159,28 @@ extern_fn! {
Some(unsafe { options.read() })
};
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output: ManuallyDrop::into_inner(out.clone()),
mvp,
let viewport = if viewport.is_null() {
Viewport::new_render_target_sized_origin(out.deref(), mvp)
.map_err(|e| LibrashaderError::D3D9FilterError(FilterChainError::Direct3DError(e)))?
} else {
let viewport = unsafe { viewport.read() };
Viewport {
x: viewport.x,
y: viewport.y,
output: out.deref(),
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
};
let options = options.map(FromUninit::from_uninit);
unsafe {
chain.frame(ManuallyDrop::into_inner(image.clone()), &viewport, frame_count, options.as_ref())?;
chain.frame(image.deref(), &viewport, frame_count, options.as_ref())?;
}
}
}
@ -170,15 +196,15 @@ extern_fn! {
chain: *mut libra_d3d9_filter_chain_t,
param_name: *const c_char,
value: f32
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
assert_non_null!(param_name);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
if chain.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
}
}
}
@ -192,18 +218,18 @@ extern_fn! {
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_d3d9_filter_chain_get_param(
chain: *mut libra_d3d9_filter_chain_t,
chain: *const libra_d3d9_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
assert_non_null!(param_name);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
let Some(value) = chain.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
};
out.write(MaybeUninit::new(value));
@ -219,9 +245,9 @@ extern_fn! {
fn libra_d3d9_filter_chain_set_active_pass_count(
chain: *mut libra_d3d9_filter_chain_t,
value: u32
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
}
}
@ -231,12 +257,12 @@ extern_fn! {
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
fn libra_d3d9_filter_chain_get_active_pass_count(
chain: *mut libra_d3d9_filter_chain_t,
chain: *const libra_d3d9_filter_chain_t,
out: *mut MaybeUninit<u32>
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
unsafe {
let value = chain.get_enabled_pass_count();
let value = chain.parameters().passes_enabled();
out.write(MaybeUninit::new(value as u32))
}
}

View file

@ -3,50 +3,42 @@ use crate::ctypes::{
};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
use librashader::runtime::gl::{
FilterChain, FilterChainOptions, FrameOptions, GLFramebuffer, GLImage,
};
use std::ffi::CStr;
use std::ffi::{c_char, c_void, CString};
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use std::slice;
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::gl::{FilterChain, FilterChainOptions, FrameOptions, GLImage};
use librashader::runtime::FilterChainParameters;
use librashader::runtime::{Size, Viewport};
use std::ffi::CStr;
use std::ffi::{c_char, c_void};
use std::mem::MaybeUninit;
use std::num::NonZeroU32;
use std::ptr::NonNull;
use std::slice;
use std::sync::Arc;
/// A GL function loader that librashader needs to be initialized with.
pub type libra_gl_loader_t = unsafe extern "system" fn(*const c_char) -> *const c_void;
/// OpenGL parameters for the source image.
/// OpenGL parameters for an image.
#[repr(C)]
pub struct libra_source_image_gl_t {
/// A texture GLuint to the source image.
pub struct libra_image_gl_t {
/// A texture GLuint to the texture.
pub handle: u32,
/// The format of the source image.
/// The format of the texture.
pub format: u32,
/// The width of the source image.
/// The width of the texture.
pub width: u32,
/// The height of the source image.
/// The height of the texture.
pub height: u32,
}
/// OpenGL parameters for the output framebuffer.
#[repr(C)]
pub struct libra_output_framebuffer_gl_t {
/// A framebuffer GLuint to the output framebuffer.
pub fbo: u32,
/// A texture GLuint to the logical buffer of the output framebuffer.
pub texture: u32,
/// The format of the output framebuffer.
pub format: u32,
}
impl From<libra_image_gl_t> for GLImage {
fn from(value: libra_image_gl_t) -> Self {
let handle = NonZeroU32::try_from(value.handle)
.ok()
.map(glow::NativeTexture);
impl From<libra_source_image_gl_t> for GLImage {
fn from(value: libra_source_image_gl_t) -> Self {
GLImage {
handle: value.handle,
handle,
format: value.format,
size: Size::new(value.width, value.height),
}
@ -64,7 +56,7 @@ pub struct frame_gl_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
@ -104,27 +96,6 @@ config_struct! {
}
}
extern_fn! {
/// Initialize the OpenGL Context for librashader.
///
/// This only has to be done once throughout the lifetime of the application,
/// unless for whatever reason you switch OpenGL loaders mid-flight.
///
/// ## Safety
/// Attempting to create a filter chain will fail if the GL context is not initialized.
///
/// Reinitializing the OpenGL context with a different loader immediately invalidates previous filter
/// chain objects, and drawing with them causes immediate undefined behaviour.
raw fn libra_gl_init_context(loader: libra_gl_loader_t) {
gl::load_with(|s| unsafe {
let proc_name = CString::new(s).unwrap_unchecked();
loader(proc_name.as_ptr())
});
LibrashaderError::ok()
}
}
extern_fn! {
/// Create the filter chain given the shader preset.
///
@ -137,6 +108,7 @@ extern_fn! {
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
fn libra_gl_filter_chain_create(
preset: *mut libra_shader_preset_t,
loader: libra_gl_loader_t,
options: *const MaybeUninit<filter_chain_gl_opt_t>,
out: *mut MaybeUninit<libra_gl_filter_chain_t>
) {
@ -156,7 +128,11 @@ extern_fn! {
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = FilterChain::load_from_preset(*preset, options.as_ref())?;
let context = glow::Context::from_loader_function_cstr(
|proc_name| loader(proc_name.as_ptr()));
let chain = FilterChain::load_from_preset(*preset,
Arc::new(context), options.as_ref())?;
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
chain,
@ -168,6 +144,23 @@ extern_fn! {
extern_fn! {
/// Draw a frame with the given parameters for the given filter chain.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `libra_image_gl_t`, containing the name of a Texture, format, and size information to
/// to an image that will serve as the source image for the frame.
/// - `out` is a `libra_output_framebuffer_gl_t`, containing the name of a Framebuffer, the name of a Texture, format,
/// and size information for the render target of the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
@ -182,14 +175,16 @@ extern_fn! {
nopanic fn libra_gl_filter_chain_frame(
chain: *mut libra_gl_filter_chain_t,
frame_count: usize,
image: libra_source_image_gl_t,
viewport: libra_viewport_t,
out: libra_output_framebuffer_gl_t,
image: libra_image_gl_t,
out: libra_image_gl_t,
viewport: *const libra_viewport_t,
mvp: *const f32,
opt: *const MaybeUninit<frame_gl_opt_t>,
) mut |chain| {
assert_some_ptr!(mut chain);
let image: GLImage = image.into();
let out: GLImage = out.into();
let mvp = if mvp.is_null() {
None
} else {
@ -202,12 +197,21 @@ extern_fn! {
};
let opt = opt.map(FromUninit::from_uninit);
let framebuffer = GLFramebuffer::new_from_raw(out.texture, out.fbo, out.format, Size::new(viewport.width, viewport.height), 1);
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output: &framebuffer,
mvp,
let viewport = if viewport.is_null() {
Viewport::new_render_target_sized_origin(&out, mvp)?
} else {
let viewport = unsafe { viewport.read() };
Viewport {
x: viewport.x,
y: viewport.y,
output: &out,
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
};
unsafe {
@ -227,15 +231,15 @@ extern_fn! {
chain: *mut libra_gl_filter_chain_t,
param_name: *const c_char,
value: f32
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
assert_non_null!(param_name);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
if chain.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
}
}
}
@ -249,18 +253,18 @@ extern_fn! {
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_gl_filter_chain_get_param(
chain: *mut libra_gl_filter_chain_t,
chain: *const libra_gl_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
assert_non_null!(param_name);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
let Some(value) = chain.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
};
out.write(MaybeUninit::new(value));
@ -276,9 +280,9 @@ extern_fn! {
fn libra_gl_filter_chain_set_active_pass_count(
chain: *mut libra_gl_filter_chain_t,
value: u32
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
}
}
@ -288,11 +292,11 @@ extern_fn! {
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
fn libra_gl_filter_chain_get_active_pass_count(
chain: *mut libra_gl_filter_chain_t,
chain: *const libra_gl_filter_chain_t,
out: *mut MaybeUninit<u32>
) mut |chain| {
assert_some_ptr!(mut chain);
let value = chain.get_enabled_pass_count();
) |chain| {
assert_some_ptr!(chain);
let value = chain.parameters().passes_enabled();
unsafe {
out.write(MaybeUninit::new(value as u32))
}
@ -305,6 +309,7 @@ extern_fn! {
/// The resulting value in `chain` then becomes null.
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
/// - The context that the filter chain was initialized with **must be current** before freeing the filter chain.
fn libra_gl_filter_chain_free(
chain: *mut libra_gl_filter_chain_t
) {

View file

@ -1,34 +1,46 @@
//! librashader runtime C APIs.
#[doc(cfg(feature = "runtime-opengl"))]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-opengl")))]
#[cfg(feature = "runtime-opengl")]
pub mod gl;
#[doc(cfg(feature = "runtime-vulkan"))]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-vulkan")))]
#[cfg(feature = "runtime-vulkan")]
pub mod vk;
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))
)]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d11")
))]
pub mod d3d11;
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))
)]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d9")
))]
pub mod d3d9;
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))
)]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d12")
))]
pub mod d3d12;
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))
)]
#[cfg(any(
feature = "__cbindgen_internal",
all(

View file

@ -38,7 +38,7 @@ pub struct frame_mtl_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
@ -173,6 +173,22 @@ extern_fn! {
extern_fn! {
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command buffer.
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `command_buffer` is a `MTLCommandBuffer` handle to record draw commands to.
/// The provided command buffer must be ready for encoding and contain no prior commands
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `id<MTLTexture>` that will serve as the source image for the frame.
/// - `out` is a `id<MTLTexture>` that is the render target of the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `command_buffer` must be a valid reference to a `MTLCommandBuffer` that is not already encoding.
@ -189,8 +205,8 @@ extern_fn! {
command_buffer: PMTLCommandBuffer,
frame_count: usize,
image: PMTLTexture,
viewport: libra_viewport_t,
output: PMTLTexture,
viewport: *const libra_viewport_t,
mvp: *const f32,
opt: *const MaybeUninit<frame_mtl_opt_t>
) |command_buffer, image, output|; mut |chain| {
@ -207,11 +223,21 @@ extern_fn! {
Some(unsafe { opt.read() })
};
let opt = opt.map(FromUninit::from_uninit);
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output,
mvp,
let viewport = if viewport.is_null() {
Viewport::new_render_target_sized_origin(output, mvp)?
} else {
let viewport = unsafe { viewport.read() };
Viewport {
x: viewport.x,
y: viewport.y,
output,
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
};
chain.frame(&image, &viewport, command_buffer, frame_count, opt.as_ref())?;
@ -229,14 +255,14 @@ extern_fn! {
chain: *mut libra_mtl_filter_chain_t,
param_name: *const c_char,
value: f32
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
if chain.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
}
}
}
@ -250,18 +276,18 @@ extern_fn! {
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_mtl_filter_chain_get_param(
chain: *mut libra_mtl_filter_chain_t,
chain: *const libra_mtl_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
let Some(value) = chain.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
};
out.write(MaybeUninit::new(value));
@ -277,9 +303,9 @@ extern_fn! {
fn libra_mtl_filter_chain_set_active_pass_count(
chain: *mut libra_mtl_filter_chain_t,
value: u32
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
}
}
@ -289,11 +315,11 @@ extern_fn! {
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
fn libra_mtl_filter_chain_get_active_pass_count(
chain: *mut libra_mtl_filter_chain_t,
chain: *const libra_mtl_filter_chain_t,
out: *mut MaybeUninit<u32>
) mut |chain| {
assert_some_ptr!(mut chain);
let value = chain.get_enabled_pass_count();
) |chain| {
assert_some_ptr!(chain);
let value = chain.parameters().passes_enabled();
unsafe {
out.write(MaybeUninit::new(value as u32))
}

View file

@ -6,8 +6,8 @@ use crate::ffi::extern_fn;
use librashader::runtime::vk::{
FilterChain, FilterChainOptions, FrameOptions, VulkanImage, VulkanInstance,
};
use std::ffi::c_char;
use std::ffi::CStr;
use std::ffi::{c_char, c_void};
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use std::slice;
@ -15,37 +15,26 @@ use std::slice;
use librashader::runtime::FilterChainParameters;
use librashader::runtime::{Size, Viewport};
use ash::vk;
use crate::LIBRASHADER_API_VERSION;
use ash::vk;
use ash::vk::Handle;
/// A Vulkan instance function loader that the Vulkan filter chain needs to be initialized with.
pub use ash::vk::PFN_vkGetInstanceProcAddr;
/// A Vulkan instance function loader that the Vulkan filter chain needs to be initialized with.
pub type libra_PFN_vkGetInstanceProcAddr =
unsafe extern "system" fn(instance: *mut c_void, p_name: *const c_char);
/// Vulkan parameters for the source image.
/// Vulkan parameters for an image.
#[repr(C)]
pub struct libra_source_image_vk_t {
/// A raw `VkImage` handle to the source image.
pub struct libra_image_vk_t {
/// A raw `VkImage` handle.
pub handle: vk::Image,
/// The `VkFormat` of the source image.
/// The `VkFormat` of the `VkImage`.
pub format: vk::Format,
/// The width of the source image.
/// The width of the `VkImage`.
pub width: u32,
/// The height of the source image.
/// The height of the `VkImage`.
pub height: u32,
}
/// Vulkan parameters for the output image.
#[repr(C)]
pub struct libra_output_image_vk_t {
/// A raw `VkImage` handle to the output image.
pub handle: vk::Image,
/// The `VkFormat` of the output image.
pub format: vk::Format,
}
/// Handles required to instantiate vulkan
#[repr(C)]
pub struct libra_device_vk_t {
@ -58,12 +47,15 @@ pub struct libra_device_vk_t {
/// A raw `VkDevice` handle
/// for the device attached to the instance that will perform rendering.
pub device: vk::Device,
/// The queue to use, if this is `NULL`, then
/// a suitable queue will be chosen. This must be a graphics queue.
pub queue: vk::Queue,
/// The entry loader for the Vulkan library.
pub entry: vk::PFN_vkGetInstanceProcAddr,
pub entry: Option<vk::PFN_vkGetInstanceProcAddr>,
}
impl From<libra_source_image_vk_t> for VulkanImage {
fn from(value: libra_source_image_vk_t) -> Self {
impl From<libra_image_vk_t> for VulkanImage {
fn from(value: libra_image_vk_t) -> Self {
VulkanImage {
size: Size::new(value.width, value.height),
image: value.handle,
@ -74,11 +66,18 @@ impl From<libra_source_image_vk_t> for VulkanImage {
impl From<libra_device_vk_t> for VulkanInstance {
fn from(value: libra_device_vk_t) -> Self {
let queue = if value.queue.is_null() {
None
} else {
Some(value.queue)
};
VulkanInstance {
device: value.device,
instance: value.instance,
physical_device: value.physical_device,
get_instance_proc_addr: value.entry,
queue,
}
}
}
@ -94,7 +93,7 @@ pub struct frame_vk_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
@ -236,13 +235,31 @@ extern_fn! {
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command buffer.
///
/// * The input image must be in the `VK_SHADER_READ_ONLY_OPTIMAL` layout.
/// * The output image must be in `VK_COLOR_ATTACHMENT_OPTIMAL` layout.
///
/// librashader **will not** create a pipeline barrier for the final pass. The output image will
/// remain in `VK_COLOR_ATTACHMENT_OPTIMAL` after all shader passes. The caller must transition
/// A pipeline barrier **will not** be created for the final pass. The output image must be
/// in `VK_COLOR_ATTACHMENT_OPTIMAL`, and will remain so after all shader passes. The caller must transition
/// the output image to the final layout.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `command_buffer` is a `VkCommandBuffer` handle to record draw commands to.
/// The provided command buffer must be ready for recording and contain no prior commands
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `libra_image_vk_t`, containing a `VkImage` handle, it's format and size information,
/// to an image that will serve as the source image for the frame. The input image must be in
/// the `VK_SHADER_READ_ONLY_OPTIMAL` layout.
/// - `out` is a `libra_image_vk_t`, containing a `VkImage` handle, it's format and size information,
/// for the render target of the frame. The output image must be in `VK_COLOR_ATTACHMENT_OPTIMAL` layout.
/// The output image will remain in `VK_COLOR_ATTACHMENT_OPTIMAL` after all shader passes.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `libra_vk_filter_chain_frame` **must not be called within a RenderPass**.
/// - `command_buffer` must be a valid handle to a `VkCommandBuffer` that is ready for recording.
@ -258,9 +275,9 @@ extern_fn! {
chain: *mut libra_vk_filter_chain_t,
command_buffer: vk::CommandBuffer,
frame_count: usize,
image: libra_source_image_vk_t,
viewport: libra_viewport_t,
out: libra_output_image_vk_t,
image: libra_image_vk_t,
out: libra_image_vk_t,
viewport: *const libra_viewport_t,
mvp: *const f32,
opt: *const MaybeUninit<frame_vk_opt_t>
) mut |chain| {
@ -268,7 +285,7 @@ extern_fn! {
let image: VulkanImage = image.into();
let output = VulkanImage {
image: out.handle,
size: Size::new(viewport.width, viewport.height),
size: Size::new(out.width, out.height),
format: out.format
};
let mvp = if mvp.is_null() {
@ -282,11 +299,21 @@ extern_fn! {
Some(unsafe { opt.read() })
};
let opt = opt.map(FromUninit::from_uninit);
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output,
mvp,
let viewport = if viewport.is_null() {
Viewport::new_render_target_sized_origin(output, mvp)?
} else {
let viewport = unsafe { viewport.read() };
Viewport {
x: viewport.x,
y: viewport.y,
output,
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
};
unsafe {
@ -306,15 +333,15 @@ extern_fn! {
chain: *mut libra_vk_filter_chain_t,
param_name: *const c_char,
value: f32
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
assert_non_null!(param_name);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
if chain.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
}
}
}
@ -328,18 +355,18 @@ extern_fn! {
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_vk_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_vk_filter_chain_get_param(
chain: *mut libra_vk_filter_chain_t,
chain: *const libra_vk_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) mut |chain| {
assert_some_ptr!(mut chain);
) |chain| {
assert_some_ptr!(chain);
assert_non_null!(param_name);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
let Some(value) = chain.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
};
out.write(MaybeUninit::new(value));
@ -355,9 +382,9 @@ extern_fn! {
fn libra_vk_filter_chain_set_active_pass_count(
chain: *mut libra_vk_filter_chain_t,
value: u32
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
}
}
@ -367,11 +394,11 @@ extern_fn! {
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_vk_filter_chain_t`.
fn libra_vk_filter_chain_get_active_pass_count(
chain: *mut libra_vk_filter_chain_t,
chain: *const libra_vk_filter_chain_t,
out: *mut MaybeUninit<u32>
) mut |chain| {
assert_some_ptr!(mut chain);
let value = chain.get_enabled_pass_count();
) |chain| {
assert_some_ptr!(chain);
let value = chain.parameters().passes_enabled();
unsafe {
out.write(MaybeUninit::new(value as u32))
}

View file

@ -2,6 +2,7 @@
/// API version type alias.
pub type LIBRASHADER_API_VERSION = usize;
/// ABI version type alias.
pub type LIBRASHADER_ABI_VERSION = usize;
/// The current version of the librashader API.
@ -27,10 +28,17 @@ pub const LIBRASHADER_CURRENT_VERSION: LIBRASHADER_API_VERSION = 1;
/// ABI versions are not backwards compatible. It is not
/// valid to load a librashader C API instance for any ABI
/// version not equal to LIBRASHADER_CURRENT_ABI.
///
/// ## ABI Versions
/// - ABI version 0: null instance (unloaded)
/// - ABI version 1: 0.1.0
pub const LIBRASHADER_CURRENT_ABI: LIBRASHADER_ABI_VERSION = 1;
/// - ABI version 2: 0.5.0
/// - Reduced texture size information needed for some runtimes.
/// - Removed wrapper structs for Direct3D 11 SRV and RTV handles.
/// - Removed `gl_context_init`.
/// - Make viewport handling consistent across runtimes, which are now
/// span the output render target if omitted.
pub const LIBRASHADER_CURRENT_ABI: LIBRASHADER_ABI_VERSION = 2;
/// Function pointer definition for libra_abi_version
pub type PFN_libra_instance_abi_version = extern "C" fn() -> LIBRASHADER_ABI_VERSION;

View file

@ -25,7 +25,7 @@ extern_fn! {
///
/// These automatically inferred variables, as well as all other variables can be overridden with
/// `libra_preset_ctx_set_param`, but the expected string values must be provided.
/// See https://github.com/libretro/RetroArch/pull/15023 for a list of expected string values.
/// See <https://github.com/libretro/RetroArch/pull/15023> for a list of expected string values.
///
/// No variables can be removed once added to the context, however subsequent calls to set the same
/// variable will overwrite the expected variable.
@ -144,6 +144,8 @@ extern_fn! {
/// - GLCore
/// - Direct3D11
/// - Direct3D12
/// - Metal
/// - Direct3D9 (HLSL)
///
/// This will also set the appropriate video driver extensions.
///

View file

@ -0,0 +1,91 @@
[package]
name = "librashader-cli"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
categories = ["emulators", "compilers", "graphics"]
keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[lib]
name = "librashader_test"
path = "src/lib.rs"
[[bin]]
name = "librashader-cli"
path = "src/cli/main.rs"
[dependencies]
librashader = { version = "0.5.1", path = "../librashader", features = [
"presets",
"preprocess",
"serde",
], default-features = false }
librashader-runtime = { version = "0.5.1", path = "../librashader-runtime" }
librashader-common = { version = "0.5.1", path = "../librashader-common" }
wgpu = { version = "22", default-features = false, optional = true }
wgpu-types = { version = "22", optional = true }
anyhow = "1.0.86"
image = { workspace = true }
gfx-maths = "0.2.8"
pollster = "0.3.0"
parking_lot = "0.12.3"
image-compare = "0.4.1"
gpu-allocator = "0.27.0"
bitvec = "1.0.1"
d3d12-descriptor-heap = { version = "0.2", optional = true }
glow = { workspace = true, optional = true }
glfw = { workspace = true, optional = true }
ash = { workspace = true, optional = true }
clap = { workspace = true }
serde = "1.0"
serde_json = "1.0"
spq-spvasm = "0.1.4"
rmp-serde = "1.3.0"
[features]
default = ["full"]
full = ["vulkan", "opengl", "wgpu", "d3d9", "d3d11", "d3d12", "metal"]
vulkan = ["librashader/runtime-vk", "dep:ash"]
opengl = ["librashader/runtime-gl", "dep:glow", "dep:glfw"]
wgpu = ["librashader/runtime-wgpu", "dep:wgpu", "dep:wgpu-types"]
d3d11 = ["librashader/runtime-d3d11", "dep:windows"]
d3d12 = [
"librashader/runtime-d3d12",
"dep:windows",
"dep:d3d12-descriptor-heap",
]
d3d9 = ["librashader/runtime-d3d9", "dep:windows"]
metal = ["librashader/runtime-metal", "dep:objc2", "dep:objc2-metal"]
vulkan-debug = ["vulkan"]
[target.'cfg(windows)'.dependencies.windows]
workspace = true
optional = true
features = [
"Win32_Foundation",
"Win32_Graphics_Dxgi_Common",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D11",
"Win32_Graphics_Direct3D_Fxc",
"Win32_Graphics_Gdi",
"Win32_Security",
"Win32_System_LibraryLoader",
"Win32_System_Threading",
"Win32_UI_WindowsAndMessaging",
"Win32_UI",
]
[target.'cfg(target_vendor="apple")'.dependencies]
objc2-metal = { version = "0.2.0", features = ["all"], optional = true }
objc2 = { version = "0.5.0", features = ["apple"], optional = true }

View file

@ -0,0 +1,804 @@
use anyhow::anyhow;
use clap::{Parser, Subcommand};
use image::codecs::png::PngEncoder;
use librashader::presets::context::ContextItem;
use librashader::presets::{ShaderPreset, ShaderPresetPack, WildcardContext};
use librashader::reflect::cross::{GlslVersion, HlslShaderModel, MslVersion, SpirvCross};
use librashader::reflect::naga::{Naga, NagaLoweringOptions};
use librashader::reflect::semantics::ShaderSemantics;
use librashader::reflect::{CompileShader, FromCompilation, ReflectShader, SpirvCompilation};
use librashader::runtime::Size;
use librashader::{FastHashMap, ShortString};
use librashader_common::StorageType;
use librashader_runtime::parameters::RuntimeParameters;
use librashader_test::render::{CommonFrameOptions, RenderTest};
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
/// Helpers and utilities to reflect and debug 'slang' shaders and presets.
#[derive(Parser, Debug)]
#[command(version, about)]
struct Args {
#[command(subcommand)]
command: Commands,
}
#[derive(clap::Args, Debug)]
struct PresetArgs {
/// The path to the shader preset to load.
#[arg(short, long)]
preset: PathBuf,
/// Additional wildcard options, comma separated with equals signs. The PRESET and PRESET_DIR
/// wildcards are always added to the preset parsing context.
///
/// For example, CONTENT-DIR=MyVerticalGames,GAME=mspacman
#[arg(short, long, value_delimiter = ',', num_args = 1..)]
wildcards: Option<Vec<String>>,
}
#[derive(clap::Args, Debug)]
struct RenderArgs {
/// The frame to render.
///
/// The renderer will run up to the number of frames specified here
/// to ensure feedback and history.
#[arg(short, long, default_value_t = 0)]
frame: usize,
/// The dimensions of the image.
///
/// This is given in either explicit dimensions `WIDTHxHEIGHT`, or a
/// percentage of the input image in `SCALE%`.
#[arg(short, long)]
dimensions: Option<String>,
/// Parameters to pass to the shader preset, comma separated with equals signs.
///
/// For example, crt_gamma=2.5,halation_weight=0.001
#[arg(long, value_delimiter = ',', num_args = 1..)]
params: Option<Vec<String>>,
/// Set the number of passes enabled for the preset.
#[arg(long)]
passes_enabled: Option<usize>,
/// The path to the input image.
#[arg(short, long)]
image: PathBuf,
#[clap(flatten)]
options: Option<FrameOptionsArgs>,
}
impl From<FrameOptionsArgs> for CommonFrameOptions {
fn from(value: FrameOptionsArgs) -> Self {
Self {
clear_history: false,
frame_direction: value.frame_direction,
rotation: value.rotation,
total_subframes: value.total_subframes,
current_subframe: value.current_subframe,
}
}
}
#[derive(clap::Args, Debug)]
struct FrameOptionsArgs {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
#[arg(long, default_value_t = 1, allow_hyphen_values = true)]
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
#[arg(long, default_value_t = 0)]
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
#[arg(long, default_value_t = 1)]
pub total_subframes: u32,
/// The current sub frame. Default is 1.
#[arg(long, default_value_t = 1)]
pub current_subframe: u32,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Render a shader preset against an image
Render {
#[clap(flatten)]
preset: PresetArgs,
#[clap(flatten)]
render: RenderArgs,
/// The path to the output image
///
/// If `-`, writes the image in PNG format to stdout.
#[arg(short, long)]
out: PathBuf,
/// The runtime to use to render the shader preset.
#[arg(value_enum, short, long)]
runtime: Runtime,
},
/// Compare two runtimes and get a similarity score between the two
/// runtimes rendering the same frame
Compare {
#[clap(flatten)]
preset: PresetArgs,
#[clap(flatten)]
render: RenderArgs,
/// The runtime to compare against
#[arg(value_enum, short, long)]
left: Runtime,
/// The runtime to compare to
#[arg(value_enum, short, long)]
right: Runtime,
/// The path to write the similarity image.
///
/// If `-`, writes the image to stdout.
#[arg(short, long)]
out: Option<PathBuf>,
},
/// Parse a preset and get a JSON representation of the data.
Parse {
#[clap(flatten)]
preset: PresetArgs,
},
/// Create a serialized preset pack from a shader preset.
Pack {
#[clap(flatten)]
preset: PresetArgs,
/// The path to write the output
///
/// If `-`, writes the output to stdout
#[arg(short, long)]
out: PathBuf,
/// The file format to output.
#[arg(value_enum, short, long)]
format: PackFormat,
},
/// Get the raw GLSL output of a preprocessed shader.
Preprocess {
/// The path to the slang shader.
#[arg(short, long)]
shader: PathBuf,
/// The item to output.
///
/// `json` will print a JSON representation of the preprocessed shader.
#[arg(value_enum, short, long)]
output: PreprocessOutput,
},
/// Transpile a shader to the given format.
Transpile {
/// The path to the slang shader.
#[arg(short, long)]
shader: PathBuf,
/// The shader stage to output
#[arg(value_enum, short = 'o', long)]
stage: TranspileStage,
/// The output format.
#[arg(value_enum, short, long)]
format: TranspileFormat,
/// The version of the output format to parse as, if applicable
///
/// For GLSL, this should be an string corresponding to a GLSL version (e.g. '330', or '300es', or '300 es').
///
/// For HLSL, this is a shader model version as an integer (50), or a version in the format MAJ_MIN (5_0), or MAJ.MIN (5.0).
///
/// For MSL, this is the shader language version as an integer in format
/// <MMmmpp>(30100), or a version in the format MAJ_MIN (3_1), or MAJ.MIN (3.1).
///
/// For SPIR-V, if this is the string "raw-id", then shows raw ID values instead of friendly names.
#[arg(short, long)]
version: Option<String>,
},
/// Reflect the shader relative to a preset, giving information about semantics used in a slang shader.
Reflect {
#[clap(flatten)]
preset: PresetArgs,
/// The pass index to use.
#[arg(short, long)]
index: usize,
#[arg(value_enum, short, long, default_value = "cross")]
backend: ReflectionBackend,
},
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum PreprocessOutput {
#[clap(name = "fragment")]
Fragment,
#[clap(name = "vertex")]
Vertex,
#[clap(name = "params")]
Params,
#[clap(name = "passformat")]
Format,
#[clap(name = "json")]
Json,
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum TranspileStage {
#[clap(name = "fragment")]
Fragment,
#[clap(name = "vertex")]
Vertex,
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum TranspileFormat {
#[clap(name = "glsl")]
GLSL,
#[clap(name = "hlsl")]
HLSL,
#[clap(name = "wgsl")]
WGSL,
#[clap(name = "msl")]
MSL,
#[clap(name = "spirv")]
SPIRV,
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum PackFormat {
#[clap(name = "json")]
JSON,
#[clap(name = "msgpack")]
MsgPack,
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum Runtime {
#[cfg(feature = "opengl")]
#[clap(name = "opengl3")]
OpenGL3,
#[cfg(feature = "opengl")]
#[clap(name = "opengl4")]
OpenGL4,
#[cfg(feature = "vulkan")]
#[clap(name = "vulkan")]
Vulkan,
#[cfg(feature = "wgpu")]
#[clap(name = "wgpu")]
Wgpu,
#[cfg(all(windows, feature = "d3d9"))]
#[clap(name = "d3d9")]
Direct3D9,
#[cfg(all(windows, feature = "d3d11"))]
#[clap(name = "d3d11")]
Direct3D11,
#[cfg(all(windows, feature = "d3d12"))]
#[clap(name = "d3d12")]
Direct3D12,
#[cfg(all(target_vendor = "apple", feature = "metal"))]
#[clap(name = "metal")]
Metal,
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum ReflectionBackend {
#[clap(name = "cross")]
SpirvCross,
#[clap(name = "naga")]
Naga,
}
macro_rules! get_runtime {
($rt:ident, $image:ident) => {
match $rt {
#[cfg(feature = "opengl")]
Runtime::OpenGL3 => &mut librashader_test::render::gl::OpenGl3::new($image.as_path())?,
#[cfg(feature = "opengl")]
Runtime::OpenGL4 => &mut librashader_test::render::gl::OpenGl4::new($image.as_path())?,
#[cfg(feature = "vulkan")]
Runtime::Vulkan => &mut librashader_test::render::vk::Vulkan::new($image.as_path())?,
#[cfg(feature = "wgpu")]
Runtime::Wgpu => &mut librashader_test::render::wgpu::Wgpu::new($image.as_path())?,
#[cfg(all(windows, feature = "d3d9"))]
Runtime::Direct3D9 => {
&mut librashader_test::render::d3d9::Direct3D9::new($image.as_path())?
}
#[cfg(all(windows, feature = "d3d11"))]
Runtime::Direct3D11 => {
&mut librashader_test::render::d3d11::Direct3D11::new($image.as_path())?
}
#[cfg(all(windows, feature = "d3d12"))]
Runtime::Direct3D12 => {
&mut librashader_test::render::d3d12::Direct3D12::new($image.as_path())?
}
#[cfg(all(target_vendor = "apple", feature = "metal"))]
Runtime::Metal => &mut librashader_test::render::mtl::Metal::new($image.as_path())?,
}
};
}
pub fn main() -> Result<(), anyhow::Error> {
let args = Args::parse();
match args.command {
Commands::Render {
preset,
render,
out,
runtime,
} => {
let PresetArgs { preset, wildcards } = preset;
let RenderArgs {
frame,
dimensions,
params,
passes_enabled,
image,
options,
} = render;
let test: &mut dyn RenderTest = get_runtime!(runtime, image);
let dimensions = parse_dimension(dimensions, test.image_size())?;
let preset = get_shader_preset(preset, wildcards)?;
let params = parse_params(params)?;
let image = test.render_with_preset_and_params(
preset,
frame,
Some(dimensions),
Some(&|rp| set_params(rp, &params, passes_enabled)),
options.map(CommonFrameOptions::from),
)?;
if out.as_path() == Path::new("-") {
let out = std::io::stdout();
image.write_with_encoder(PngEncoder::new(out))?;
} else {
image.save(out)?;
}
}
Commands::Compare {
preset,
render,
left,
right,
out,
} => {
let PresetArgs { preset, wildcards } = preset;
let RenderArgs {
frame,
dimensions,
params,
passes_enabled,
image,
options,
} = render;
let left: &mut dyn RenderTest = get_runtime!(left, image);
let right: &mut dyn RenderTest = get_runtime!(right, image);
let dimensions = parse_dimension(dimensions, left.image_size())?;
let params = parse_params(params)?;
let left_preset = get_shader_preset(preset.clone(), wildcards.clone())?;
let left_image = left.render_with_preset_and_params(
left_preset,
frame,
Some(dimensions),
Some(&|rp| set_params(rp, &params, passes_enabled)),
None,
)?;
let right_preset = get_shader_preset(preset.clone(), wildcards.clone())?;
let right_image = right.render_with_preset_and_params(
right_preset,
frame,
Some(dimensions),
Some(&|rp| set_params(rp, &params, passes_enabled)),
options.map(CommonFrameOptions::from),
)?;
let similarity = image_compare::rgba_hybrid_compare(&left_image, &right_image)?;
print!("{}", similarity.score);
if let Some(out) = out {
let image = similarity.image.to_color_map();
if out.as_path() == Path::new("-") {
let out = std::io::stdout();
image.write_with_encoder(PngEncoder::new(out))?;
} else {
image.save(out)?;
}
}
}
Commands::Parse { preset } => {
let PresetArgs { preset, wildcards } = preset;
let preset = get_shader_preset(preset, wildcards)?;
let out = serde_json::to_string_pretty(&preset)?;
print!("{out:}");
}
Commands::Preprocess { shader, output } => {
let source = librashader::preprocess::ShaderSource::load(&StorageType::Path(shader))?;
match output {
PreprocessOutput::Fragment => print!("{}", source.fragment),
PreprocessOutput::Vertex => print!("{}", source.vertex),
PreprocessOutput::Params => {
print!("{}", serde_json::to_string_pretty(&source.parameters)?)
}
PreprocessOutput::Format => print!("{:?}", source.format),
PreprocessOutput::Json => print!("{}", serde_json::to_string_pretty(&source)?),
}
}
Commands::Transpile {
shader,
stage,
format,
version,
} => {
let source = librashader::preprocess::ShaderSource::load(&StorageType::Path(shader))?;
let compilation = SpirvCompilation::try_from(&source)?;
let output = match format {
TranspileFormat::GLSL => {
let mut compilation =
librashader::reflect::targets::GLSL::from_compilation(compilation)?;
compilation.validate()?;
let version = version
.map(|s| parse_glsl_version(&s))
.unwrap_or(Ok(GlslVersion::Glsl330))?;
let output = compilation.compile(version)?;
TranspileOutput {
vertex: output.vertex,
fragment: output.fragment,
}
}
TranspileFormat::HLSL => {
let mut compilation =
librashader::reflect::targets::HLSL::from_compilation(compilation)?;
compilation.validate()?;
let shader_model = version
.map(|s| parse_hlsl_version(&s))
.unwrap_or(Ok(HlslShaderModel::ShaderModel5_0))?;
let output = compilation.compile(Some(shader_model))?;
TranspileOutput {
vertex: output.vertex,
fragment: output.fragment,
}
}
TranspileFormat::WGSL => {
let mut compilation =
librashader::reflect::targets::WGSL::from_compilation(compilation)?;
compilation.validate()?;
let output = compilation.compile(NagaLoweringOptions {
write_pcb_as_ubo: true,
sampler_bind_group: 1,
})?;
TranspileOutput {
vertex: output.vertex,
fragment: output.fragment,
}
}
TranspileFormat::MSL => {
let mut compilation =
<librashader::reflect::targets::MSL as FromCompilation<
SpirvCompilation,
SpirvCross,
>>::from_compilation(compilation)?;
compilation.validate()?;
let version = version
.map(|s| parse_msl_version(&s))
.unwrap_or(Ok(MslVersion::new(1, 2, 0)))?;
let output = compilation.compile(Some(version))?;
TranspileOutput {
vertex: output.vertex,
fragment: output.fragment,
}
}
TranspileFormat::SPIRV => {
let mut compilation =
<librashader::reflect::targets::SPIRV as FromCompilation<
SpirvCompilation,
SpirvCross,
>>::from_compilation(compilation)?;
compilation.validate()?;
let output = compilation.compile(None)?;
let raw = version.is_some_and(|s| s == "raw-id");
TranspileOutput {
vertex: spirv_to_dis(output.vertex, raw)?,
fragment: spirv_to_dis(output.fragment, raw)?,
}
}
};
let print = match stage {
TranspileStage::Fragment => output.fragment,
TranspileStage::Vertex => output.vertex,
};
print!("{print}")
}
Commands::Reflect {
preset,
index,
backend,
} => {
let PresetArgs { preset, wildcards } = preset;
let preset = get_shader_preset(preset, wildcards)?;
let Some(shader) = preset.passes.get(index) else {
return Err(anyhow!("Invalid pass index for the preset"));
};
let source = librashader::preprocess::ShaderSource::load(&shader.storage)?;
let compilation = SpirvCompilation::try_from(&source)?;
let semantics =
ShaderSemantics::create_pass_semantics::<anyhow::Error>(&preset, index)?;
let reflection = match backend {
ReflectionBackend::SpirvCross => {
let mut compilation =
<librashader::reflect::targets::SPIRV as FromCompilation<
SpirvCompilation,
SpirvCross,
>>::from_compilation(compilation)?;
compilation.reflect(index, &semantics)?
}
ReflectionBackend::Naga => {
let mut compilation =
<librashader::reflect::targets::SPIRV as FromCompilation<
SpirvCompilation,
Naga,
>>::from_compilation(compilation)?;
compilation.reflect(index, &semantics)?
}
};
print!("{}", serde_json::to_string_pretty(&reflection)?);
}
Commands::Pack {
preset,
out,
format,
} => {
let PresetArgs { preset, wildcards } = preset;
let preset = get_shader_preset(preset, wildcards)?;
let preset = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset)?;
let output_bytes = match format {
PackFormat::JSON => serde_json::to_vec_pretty(&preset)?,
PackFormat::MsgPack => rmp_serde::to_vec(&preset)?,
};
if out.as_path() == Path::new("-") {
let mut out = std::io::stdout();
out.write_all(output_bytes.as_slice())?;
} else {
let mut file = File::create(out.as_path())?;
file.write_all(output_bytes.as_slice())?;
}
}
}
Ok(())
}
struct TranspileOutput {
vertex: String,
fragment: String,
}
fn get_shader_preset(
preset: PathBuf,
wildcards: Option<Vec<String>>,
) -> anyhow::Result<ShaderPreset> {
let mut context = WildcardContext::new();
context.add_path_defaults(preset.as_path());
if let Some(wildcards) = wildcards {
for string in wildcards {
let Some((left, right)) = string.split_once("=") else {
return Err(anyhow!("Encountered invalid context string {string}"));
};
context.append_item(ContextItem::ExternContext(
left.to_string(),
right.to_string(),
))
}
}
let preset = ShaderPreset::try_parse_with_context(preset, context)?;
Ok(preset)
}
fn parse_params(
assignments: Option<Vec<String>>,
) -> anyhow::Result<Option<FastHashMap<ShortString, f32>>> {
let Some(assignments) = assignments else {
return Ok(None);
};
let mut map = FastHashMap::default();
for string in assignments {
let Some((left, right)) = string.split_once("=") else {
return Err(anyhow!("Encountered invalid parameter string {string}"));
};
let value = right
.parse::<f32>()
.map_err(|_| anyhow!("Encountered invalid parameter value: {right}"))?;
map.insert(ShortString::from(left), value);
}
Ok(Some(map))
}
fn set_params(
params: &RuntimeParameters,
assignments: &Option<FastHashMap<ShortString, f32>>,
passes_enabled: Option<usize>,
) {
if let Some(passes_enabled) = passes_enabled {
params.set_passes_enabled(passes_enabled)
};
let Some(assignments) = assignments else {
return;
};
params.update_parameters(|params| {
for (key, param) in assignments {
params.insert(key.clone(), *param);
}
});
}
fn spirv_to_dis(spirv: Vec<u32>, raw: bool) -> anyhow::Result<String> {
let binary = spq_spvasm::SpirvBinary::from(spirv);
spq_spvasm::Disassembler::new()
.print_header(true)
.name_ids(!raw)
.name_type_ids(!raw)
.name_const_ids(!raw)
.indent(true)
.disassemble(&binary)
}
fn parse_glsl_version(version_str: &str) -> anyhow::Result<GlslVersion> {
if version_str.contains("es") {
let Some(version) = version_str.strip_suffix("es").map(|s| s.trim()) else {
return Err(anyhow!("Unknown GLSL version"));
};
Ok(match version {
"100" => GlslVersion::Glsl100Es,
"300" => GlslVersion::Glsl300Es,
"310" => GlslVersion::Glsl310Es,
"320" => GlslVersion::Glsl320Es,
_ => return Err(anyhow!("Unknown GLSL version")),
})
} else {
Ok(match version_str {
"100" => GlslVersion::Glsl100Es,
"110" => GlslVersion::Glsl110,
"120" => GlslVersion::Glsl120,
"130" => GlslVersion::Glsl130,
"140" => GlslVersion::Glsl140,
"150" => GlslVersion::Glsl150,
"300" => GlslVersion::Glsl300Es,
"330" => GlslVersion::Glsl330,
"310" => GlslVersion::Glsl310Es,
"320" => GlslVersion::Glsl320Es,
"400" => GlslVersion::Glsl400,
"410" => GlslVersion::Glsl410,
"420" => GlslVersion::Glsl420,
"430" => GlslVersion::Glsl430,
"440" => GlslVersion::Glsl440,
"450" => GlslVersion::Glsl450,
"460" => GlslVersion::Glsl460,
_ => return Err(anyhow!("Unknown GLSL version")),
})
}
}
fn version_to_usize(version_str: &str) -> anyhow::Result<usize> {
let version: &str = if version_str.contains("_") {
&version_str.replace("_", "")
} else if version_str.contains(".") {
&version_str.replace(".", "")
} else {
version_str
};
let version = version
.parse::<usize>()
.map_err(|_| anyhow!("Invalid version string"))?;
Ok(version)
}
fn parse_hlsl_version(version_str: &str) -> anyhow::Result<HlslShaderModel> {
let version = version_to_usize(version_str)?;
Ok(match version {
30 => HlslShaderModel::ShaderModel3_0,
40 => HlslShaderModel::ShaderModel4_0,
50 => HlslShaderModel::ShaderModel5_0,
51 => HlslShaderModel::ShaderModel5_1,
60 => HlslShaderModel::ShaderModel6_0,
61 => HlslShaderModel::ShaderModel6_1,
62 => HlslShaderModel::ShaderModel6_2,
63 => HlslShaderModel::ShaderModel6_3,
64 => HlslShaderModel::ShaderModel6_4,
65 => HlslShaderModel::ShaderModel6_5,
66 => HlslShaderModel::ShaderModel6_6,
67 => HlslShaderModel::ShaderModel6_7,
68 => HlslShaderModel::ShaderModel6_8,
_ => return Err(anyhow!("Unknown Shader Model")),
})
}
fn parse_msl_version(version_str: &str) -> anyhow::Result<MslVersion> {
let version = version_to_usize(version_str)?;
Ok(match version {
10 => MslVersion::new(1, 0, 0),
11 => MslVersion::new(1, 1, 0),
12 => MslVersion::new(1, 2, 0),
20 => MslVersion::new(2, 0, 0),
21 => MslVersion::new(2, 1, 0),
22 => MslVersion::new(2, 2, 0),
23 => MslVersion::new(2, 3, 0),
24 => MslVersion::new(2, 4, 0),
30 => MslVersion::new(3, 0, 0),
31 => MslVersion::new(3, 1, 0),
32 => MslVersion::new(3, 2, 0),
n if n >= 10000 => {
let major = n / 10000;
let minor = (n - (major * 10000)) / 100;
let patch = n - ((major * 10000) + (minor * 100));
MslVersion::new(major as u32, minor as u32, patch as u32)
}
_ => return Err(anyhow!("Unknown MSL version")),
})
}
fn parse_dimension(dimstr: Option<String>, image_dim: Size<u32>) -> anyhow::Result<Size<u32>> {
let Some(dimstr) = dimstr else {
return Ok(image_dim);
};
if dimstr.contains("x") {
if let Some((Ok(width), Ok(height))) = dimstr
.split_once("x")
.map(|(width, height)| (width.parse::<u32>(), height.parse::<u32>()))
{
if width < 1 || height < 1 {
return Err(anyhow!("Dimensions must be larger than 1x1"));
}
if width > 16384 || height > 16384 {
return Err(anyhow!("Dimensions must not be larger than 16384x16384"));
}
return Ok(Size::new(width, height));
}
}
if dimstr.ends_with("%") && dimstr.len() > 1 {
if let Ok(percent) = dimstr.trim_end_matches("%").parse::<u32>() {
let percent = percent as f32 / 100f32;
let width = (image_dim.width as f32 * percent) as u32;
let height = (image_dim.height as f32 * percent) as u32;
if width < 1 || height < 1 {
return Err(anyhow!("Dimensions must be larger than 1x1"));
}
if width > 16384 || height > 16384 {
return Err(anyhow!("Dimensions must not be larger than 16384x16384"));
}
return Ok(Size { width, height });
}
}
Err(anyhow!(
"Invalid dimension syntax, must either in form WIDTHxHEIGHT or SCALE%"
))
}

View file

@ -0,0 +1,2 @@
/// Render tests
pub mod render;

View file

@ -0,0 +1,258 @@
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use image::RgbaImage;
use librashader::runtime::d3d11::*;
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use std::io::{Cursor, Write};
use std::ops::DerefMut;
use std::path::Path;
impl RenderTest for Direct3D11 {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
Direct3D11::new(path)
}
fn image_size(&self) -> Size<u32> {
self.image_bytes.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
let output_size = output_size.unwrap_or(self.image_bytes.size);
let (renderbuffer, rtv) = self.create_renderbuffer(output_size)?;
unsafe {
let mut filter_chain = FilterChain::load_from_preset(
preset,
&self.device,
Some(&FilterChainOptions {
force_no_mipmaps: false,
disable_cache: false,
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let viewport = Viewport::new_render_target_sized_origin(&rtv, None)?;
let options = frame_options.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
});
for frame in 0..=frame_count {
filter_chain.frame(None, &self.image_srv, &viewport, frame, options.as_ref())?;
}
let mut renderbuffer_desc = Default::default();
self.immediate_context.Flush();
renderbuffer.GetDesc(&mut renderbuffer_desc);
eprintln!("{:?}", renderbuffer_desc);
let mut staging = None;
self.device.CreateTexture2D(
&D3D11_TEXTURE2D_DESC {
MipLevels: 1,
BindFlags: 0,
MiscFlags: 0,
Usage: D3D11_USAGE_STAGING,
CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32,
..renderbuffer_desc
},
None,
Some(&mut staging),
)?;
let staging = staging.ok_or(anyhow!("Unable to create staging texture"))?;
self.immediate_context.CopyResource(&staging, &renderbuffer);
let mut pixels: Vec<u8> = Vec::new();
let mut map_info = Default::default();
self.immediate_context
.Map(&staging, 0, D3D11_MAP_READ, 0, Some(&mut map_info))?;
let slice = std::slice::from_raw_parts(
map_info.pData as *const u8,
(renderbuffer_desc.Height * map_info.RowPitch) as usize,
);
pixels.resize(
(renderbuffer_desc.Height * renderbuffer_desc.Width * 4) as usize,
0,
);
let mut cursor = Cursor::new(pixels.deref_mut());
for chunk in slice.chunks(map_info.RowPitch as usize) {
cursor.write_all(&chunk[..(renderbuffer_desc.Width * 4) as usize])?
}
let image = RgbaImage::from_raw(output_size.width, output_size.height, pixels)
.ok_or(anyhow!("Unable to create image from data"))?;
self.immediate_context.Unmap(&staging, 0);
Ok(image)
}
}
}
use librashader::presets::ShaderPreset;
use librashader_runtime::image::{Image, UVDirection};
use windows::{
Win32::Foundation::*, Win32::Graphics::Direct3D::*, Win32::Graphics::Direct3D11::*,
Win32::Graphics::Dxgi::Common::*, Win32::Graphics::Dxgi::*,
};
pub struct Direct3D11 {
device: ID3D11Device,
immediate_context: ID3D11DeviceContext,
_image_tex: ID3D11Texture2D,
image_srv: ID3D11ShaderResourceView,
image_bytes: Image,
}
impl Direct3D11 {
fn create_device() -> anyhow::Result<(IDXGIFactory4, ID3D11Device, ID3D11DeviceContext)> {
let dxgi_factory: IDXGIFactory4 = unsafe { CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG) }?;
let feature_levels = vec![D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1];
let mut out_device = None;
let mut out_context = None;
let mut _out_feature_level = D3D_FEATURE_LEVEL_11_0;
unsafe {
D3D11CreateDevice(
None,
D3D_DRIVER_TYPE_HARDWARE,
HMODULE::default(),
D3D11_CREATE_DEVICE_DEBUG,
Some(&feature_levels),
D3D11_SDK_VERSION,
Some(&mut out_device),
Some(&mut _out_feature_level),
Some(&mut out_context),
)
}?;
Ok((dxgi_factory, out_device.unwrap(), out_context.unwrap()))
}
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
let (_factory, device, imm_context) = Self::create_device()?;
let (image, image_tex, srv) = Self::load_image(&device, image_path)?;
Ok(Self {
device,
immediate_context: imm_context,
image_bytes: image,
_image_tex: image_tex,
image_srv: srv,
})
}
fn load_image(
device: &ID3D11Device,
image_path: &Path,
) -> anyhow::Result<(Image, ID3D11Texture2D, ID3D11ShaderResourceView)> {
let image = Image::load(image_path, UVDirection::TopLeft)?;
let desc = D3D11_TEXTURE2D_DESC {
Width: image.size.width,
Height: image.size.height,
// todo: set this to 0
MipLevels: 1,
ArraySize: 1,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
CPUAccessFlags: 0,
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
Usage: D3D11_USAGE_DEFAULT,
BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32,
..Default::default()
};
unsafe {
let mut resource = None;
device.CreateTexture2D(
&desc,
Some(&D3D11_SUBRESOURCE_DATA {
pSysMem: image.bytes.as_ptr().cast(),
SysMemPitch: image.pitch as u32,
SysMemSlicePitch: 0,
}),
Some(&mut resource),
)?;
let resource = resource.ok_or(anyhow!("Failed to create texture"))?;
let mut srv = None;
device.CreateShaderResourceView(
&resource,
Some(&D3D11_SHADER_RESOURCE_VIEW_DESC {
Format: desc.Format,
ViewDimension: D3D_SRV_DIMENSION_TEXTURE2D,
Anonymous: D3D11_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2D: D3D11_TEX2D_SRV {
MostDetailedMip: 0,
MipLevels: u32::MAX,
},
},
}),
Some(&mut srv),
)?;
let srv = srv.ok_or(anyhow!("Failed to create SRV"))?;
Ok((image, resource, srv))
}
}
fn create_renderbuffer(
&self,
size: Size<u32>,
) -> anyhow::Result<(ID3D11Texture2D, ID3D11RenderTargetView)> {
let desc = D3D11_TEXTURE2D_DESC {
Width: size.width,
Height: size.height,
// todo: set this to 0
MipLevels: 1,
ArraySize: 1,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
CPUAccessFlags: 0,
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
Usage: D3D11_USAGE_DEFAULT,
BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32,
..Default::default()
};
unsafe {
let mut resource = None;
self.device
.CreateTexture2D(&desc, None, Some(&mut resource))?;
let resource = resource.ok_or(anyhow!("Failed to create texture"))?;
let mut rtv = None;
self.device
.CreateRenderTargetView(&resource, None, Some(&mut rtv))?;
let rtv = rtv.ok_or(anyhow!("Failed to create RTV"))?;
Ok((resource, rtv))
}
}
}

View file

@ -0,0 +1,35 @@
use d3d12_descriptor_heap::D3D12DescriptorHeapType;
use windows::Win32::Graphics::Direct3D12::{
D3D12_DESCRIPTOR_HEAP_DESC, D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
};
#[derive(Clone)]
pub struct CpuStagingHeap;
#[derive(Clone)]
pub struct RenderTargetHeap;
impl D3D12DescriptorHeapType for RenderTargetHeap {
// Lut texture heaps are CPU only and get bound to the descriptor heap of the shader.
fn create_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
D3D12_DESCRIPTOR_HEAP_DESC {
Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
NumDescriptors: size as u32,
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
NodeMask: 0,
}
}
}
impl D3D12DescriptorHeapType for CpuStagingHeap {
// Lut texture heaps are CPU only and get bound to the descriptor heap of the shader.
fn create_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
D3D12_DESCRIPTOR_HEAP_DESC {
Type: D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
NumDescriptors: size as u32,
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
NodeMask: 0,
}
}
}

View file

@ -0,0 +1,426 @@
mod descriptor_heap;
mod util;
use crate::render::d3d12::descriptor_heap::{CpuStagingHeap, RenderTargetHeap};
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use d3d12_descriptor_heap::{D3D12DescriptorHeap, D3D12DescriptorHeapSlot};
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::d3d12::{D3D12OutputView, FilterChain, FilterChainOptions, FrameOptions};
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8};
use std::path::Path;
use windows::core::Interface;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Graphics::Direct3D::{D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_12_1};
use windows::Win32::Graphics::Direct3D12::{
D3D12CreateDevice, ID3D12CommandAllocator, ID3D12CommandQueue, ID3D12Device, ID3D12Fence,
ID3D12GraphicsCommandList, ID3D12Resource, D3D12_COMMAND_LIST_TYPE_DIRECT,
D3D12_COMMAND_QUEUE_DESC, D3D12_COMMAND_QUEUE_FLAG_NONE, D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE, D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
D3D12_FENCE_FLAG_NONE, D3D12_HEAP_FLAG_NONE, D3D12_HEAP_PROPERTIES, D3D12_HEAP_TYPE_CUSTOM,
D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_TYPE_UPLOAD, D3D12_MEMORY_POOL_L0,
D3D12_MEMORY_POOL_UNKNOWN, D3D12_PLACED_SUBRESOURCE_FOOTPRINT, D3D12_RESOURCE_DESC,
D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_RESOURCE_DIMENSION_TEXTURE2D,
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_SHADER_RESOURCE_VIEW_DESC, D3D12_SHADER_RESOURCE_VIEW_DESC_0,
D3D12_SRV_DIMENSION_TEXTURE2D, D3D12_SUBRESOURCE_DATA, D3D12_TEX2D_SRV,
D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
};
use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SAMPLE_DESC};
use windows::Win32::Graphics::Dxgi::{
CreateDXGIFactory2, IDXGIAdapter1, IDXGIFactory4, DXGI_ADAPTER_FLAG_NONE,
DXGI_ADAPTER_FLAG_SOFTWARE, DXGI_CREATE_FACTORY_DEBUG,
};
use windows::Win32::System::Threading::{CreateEventA, WaitForSingleObject, INFINITE};
pub struct Direct3D12 {
device: ID3D12Device,
_cpu_heap: D3D12DescriptorHeap<CpuStagingHeap>,
rtv_heap: D3D12DescriptorHeap<RenderTargetHeap>,
texture: ID3D12Resource,
_heap_slot: D3D12DescriptorHeapSlot<CpuStagingHeap>,
command_pool: ID3D12CommandAllocator,
queue: ID3D12CommandQueue,
image: Image<BGRA8>,
}
impl RenderTest for Direct3D12 {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
Direct3D12::new(path)
}
fn image_size(&self) -> Size<u32> {
self.image.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
unsafe {
let descriptor = self.rtv_heap.allocate_descriptor()?;
let cmd: ID3D12GraphicsCommandList = self.device.CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
&self.command_pool,
None,
)?;
let fence_event = CreateEventA(None, false, false, None)?;
let fence: ID3D12Fence = self.device.CreateFence(0, D3D12_FENCE_FLAG_NONE)?;
let mut filter_chain = FilterChain::load_from_preset(
preset,
&self.device,
Some(&FilterChainOptions {
force_hlsl_pipeline: false,
force_no_mipmaps: false,
disable_cache: false,
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let output_size = output_size.unwrap_or(self.image.size);
let mut output_texture = None;
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Alignment: 0,
Width: output_size.width as u64,
Height: output_size.height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: Default::default(),
Flags: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
};
// We only need to draw one frame, so don't worry about the performance impact
// of this heap.
self.device.CreateCommittedResource(
&D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_CUSTOM,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE,
MemoryPoolPreference: D3D12_MEMORY_POOL_L0,
CreationNodeMask: 1,
VisibleNodeMask: 1,
},
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_COMMON | D3D12_RESOURCE_STATE_RENDER_TARGET,
None,
&mut output_texture,
)?;
let output_texture: ID3D12Resource =
output_texture.ok_or_else(|| anyhow!("Failed to allocate resource"))?;
self.device
.CreateRenderTargetView(&output_texture, None, *descriptor.as_ref());
let viewport = Viewport::new_render_target_sized_origin(
D3D12OutputView::new_from_raw(
*descriptor.as_ref(),
output_size,
DXGI_FORMAT_B8G8R8A8_UNORM,
),
None,
)?;
let options = frame_options.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
});
let image = self.texture.to_ref();
for frame in 0..=frame_count {
filter_chain.frame(&cmd, image.into(), &viewport, frame, options.as_ref())?;
}
cmd.Close()?;
self.queue.ExecuteCommandLists(&[Some(cmd.cast()?)]);
self.queue.Signal(&fence, 1)?;
if fence.GetCompletedValue() < 1 {
fence.SetEventOnCompletion(1, fence_event)?;
WaitForSingleObject(fence_event, INFINITE);
CloseHandle(fence_event)?;
};
let mut buffer = vec![0u8; (output_size.height * output_size.width) as usize * 4];
output_texture.ReadFromSubresource(
buffer.as_mut_ptr().cast(),
4 * output_size.width,
0,
0,
None,
)?;
BGRA8::convert(&mut buffer);
let image =
RgbaImage::from_raw(output_size.width, output_size.height, Vec::from(buffer))
.ok_or(anyhow!("Unable to create image from data"))?;
Ok(image)
}
}
}
impl Direct3D12 {
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
let device = Self::create_device()?;
let mut heap = unsafe { D3D12DescriptorHeap::new(&device, 8)? };
let rtv_heap = unsafe { D3D12DescriptorHeap::new(&device, 16)? };
unsafe {
let command_pool: ID3D12CommandAllocator =
device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT)?;
let queue: ID3D12CommandQueue =
device.CreateCommandQueue(&D3D12_COMMAND_QUEUE_DESC {
Type: D3D12_COMMAND_LIST_TYPE_DIRECT,
Priority: 0,
Flags: D3D12_COMMAND_QUEUE_FLAG_NONE,
NodeMask: 0,
})?;
let (image, texture, heap_slot) =
Self::load_image(&device, &command_pool, &queue, &mut heap, image_path)?;
Ok(Self {
device,
_cpu_heap: heap,
rtv_heap,
texture,
_heap_slot: heap_slot,
command_pool,
image,
queue,
})
}
}
fn create_device() -> anyhow::Result<ID3D12Device> {
let dxgi_factory_flags = DXGI_CREATE_FACTORY_DEBUG;
let dxgi_factory: IDXGIFactory4 = unsafe { CreateDXGIFactory2(dxgi_factory_flags) }?;
let adapter = Self::get_hardware_adapter(&dxgi_factory)?;
let mut device: Option<ID3D12Device> = None;
unsafe { D3D12CreateDevice(&adapter, D3D_FEATURE_LEVEL_12_1, &mut device) }?;
let device = device.ok_or(anyhow!("Failed to initialize D3D12 device"))?;
Ok(device)
}
fn load_image(
device: &ID3D12Device,
command_pool: &ID3D12CommandAllocator,
queue: &ID3D12CommandQueue,
heap: &mut D3D12DescriptorHeap<CpuStagingHeap>,
path: &Path,
) -> anyhow::Result<(
Image<BGRA8>,
ID3D12Resource,
D3D12DescriptorHeapSlot<CpuStagingHeap>,
)> {
// 1 time queue infrastructure for lut uploads
let image: Image<BGRA8> = Image::load(path, UVDirection::TopLeft)?;
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Alignment: 0,
Width: image.size.width as u64,
Height: image.size.height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: Default::default(),
Flags: Default::default(),
};
let descriptor = heap.allocate_descriptor()?;
// create handles on GPU
let mut resource: Option<ID3D12Resource> = None;
unsafe {
let cmd: ID3D12GraphicsCommandList = device.CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
&command_pool.clone(),
None,
)?;
let fence_event = CreateEventA(None, false, false, None)?;
let fence: ID3D12Fence = device.CreateFence(0, D3D12_FENCE_FLAG_NONE)?;
device.CreateCommittedResource(
&D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 1,
VisibleNodeMask: 1,
},
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
None,
&mut resource,
)?;
let resource = resource.ok_or_else(|| anyhow!("Failed to allocate resource"))?;
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: desc.Format,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2D: D3D12_TEX2D_SRV {
MipLevels: u32::MAX,
..Default::default()
},
},
};
device.CreateShaderResourceView(&resource, Some(&srv_desc), *descriptor.as_ref());
let mut buffer_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
..Default::default()
};
let mut layout = D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default();
let mut total = 0;
// texture upload
device.GetCopyableFootprints(
&desc,
0,
1,
0,
Some(&mut layout),
None,
None,
Some(&mut total),
);
buffer_desc.Width = total;
buffer_desc.Height = 1;
buffer_desc.DepthOrArraySize = 1;
buffer_desc.MipLevels = 1;
buffer_desc.SampleDesc.Count = 1;
buffer_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
let mut upload: Option<ID3D12Resource> = None;
device.CreateCommittedResource(
&D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_UPLOAD,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 1,
VisibleNodeMask: 1,
},
D3D12_HEAP_FLAG_NONE,
&buffer_desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
None,
&mut upload,
)?;
let upload = upload.ok_or_else(|| anyhow!("Failed to allocate copy texture"))?;
let subresource = [D3D12_SUBRESOURCE_DATA {
pData: image.bytes.as_ptr().cast(),
RowPitch: 4 * image.size.width as isize,
SlicePitch: (4 * image.size.width * image.size.height) as isize,
}];
util::d3d12_resource_transition(
&cmd,
&resource,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_COPY_DEST,
);
util::d3d12_update_subresources(&cmd, &resource, &upload, 0, 0, 1, &subresource)?;
util::d3d12_resource_transition(
&cmd,
&resource,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
cmd.Close()?;
queue.ExecuteCommandLists(&[Some(cmd.cast()?)]);
queue.Signal(&fence, 1)?;
if fence.GetCompletedValue() < 1 {
fence.SetEventOnCompletion(1, fence_event)?;
WaitForSingleObject(fence_event, INFINITE);
CloseHandle(fence_event)?;
}
Ok((image, resource, descriptor))
}
}
fn get_hardware_adapter(factory: &IDXGIFactory4) -> windows::core::Result<IDXGIAdapter1> {
for i in 0.. {
let adapter = unsafe { factory.EnumAdapters1(i)? };
let desc = unsafe { adapter.GetDesc1()? };
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32) != DXGI_ADAPTER_FLAG_NONE.0 as u32
{
// Don't select the Basic Render Driver adapter. If you want a
// software adapter, pass in "/warp" on the command line.
continue;
}
// Check to see whether the adapter supports Direct3D 12, but don't
// create the actual device yet.
if unsafe {
D3D12CreateDevice(
&adapter,
D3D_FEATURE_LEVEL_11_0,
std::ptr::null_mut::<Option<ID3D12Device>>(),
)
}
.is_ok()
{
return Ok(adapter);
}
}
// Fallback to warp
unsafe { factory.EnumWarpAdapter() }
}
}

View file

@ -0,0 +1,213 @@
use anyhow::anyhow;
use std::mem::ManuallyDrop;
use windows::Win32::Graphics::Direct3D12::{
ID3D12Device, ID3D12GraphicsCommandList, ID3D12Resource, D3D12_MEMCPY_DEST,
D3D12_PLACED_SUBRESOURCE_FOOTPRINT, D3D12_RESOURCE_BARRIER, D3D12_RESOURCE_BARRIER_0,
D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, D3D12_RESOURCE_BARRIER_FLAG_NONE,
D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_RESOURCE_STATES,
D3D12_RESOURCE_TRANSITION_BARRIER, D3D12_SUBRESOURCE_DATA, D3D12_TEXTURE_COPY_LOCATION,
D3D12_TEXTURE_COPY_LOCATION_0, D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
};
#[inline(always)]
pub fn d3d12_resource_transition(
cmd: &ID3D12GraphicsCommandList,
resource: &ID3D12Resource,
before: D3D12_RESOURCE_STATES,
after: D3D12_RESOURCE_STATES,
) {
d3d12_resource_transition_subresource(
cmd,
resource,
before,
after,
D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
);
}
pub fn d3d12_get_resource_transition_subresource(
resource: &ID3D12Resource,
before: D3D12_RESOURCE_STATES,
after: D3D12_RESOURCE_STATES,
subresource: u32,
) -> D3D12_RESOURCE_BARRIER {
D3D12_RESOURCE_BARRIER {
Type: D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE,
Anonymous: D3D12_RESOURCE_BARRIER_0 {
Transition: ManuallyDrop::new(D3D12_RESOURCE_TRANSITION_BARRIER {
pResource: ManuallyDrop::new(Some(resource.clone())),
Subresource: subresource,
StateBefore: before,
StateAfter: after,
}),
},
}
}
#[inline(always)]
pub fn d3d12_resource_transition_subresource(
cmd: &ID3D12GraphicsCommandList,
resource: &ID3D12Resource,
before: D3D12_RESOURCE_STATES,
after: D3D12_RESOURCE_STATES,
subresource: u32,
) {
let barrier = [d3d12_get_resource_transition_subresource(
resource,
before,
after,
subresource,
)];
unsafe { cmd.ResourceBarrier(&barrier) }
}
pub(crate) fn d3d12_update_subresources(
cmd: &ID3D12GraphicsCommandList,
destination_resource: &ID3D12Resource,
intermediate_resource: &ID3D12Resource,
intermediate_offset: u64,
first_subresouce: u32,
num_subresources: u32,
source: &[D3D12_SUBRESOURCE_DATA],
) -> anyhow::Result<u64> {
// let allocation_size = std::mem::size_of::<D3D12_PLACED_SUBRESOURCE_FOOTPRINT>()
// + std::mem::size_of::<u32>()
// + std::mem::size_of::<u64>() * num_subresources;
unsafe {
let destination_desc = destination_resource.GetDesc();
let mut device: Option<ID3D12Device> = None;
destination_resource.GetDevice(&mut device)?;
let device = device.ok_or_else(|| anyhow!("Unable to get device"))?;
let mut layouts =
vec![D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default(); num_subresources as usize];
let mut num_rows = vec![0; num_subresources as usize];
let mut row_sizes_in_bytes = vec![0; num_subresources as usize];
let mut required_size = 0;
// texture upload
device.GetCopyableFootprints(
&destination_desc,
first_subresouce,
num_subresources,
intermediate_offset,
Some(layouts.as_mut_ptr()),
Some(num_rows.as_mut_ptr()),
Some(row_sizes_in_bytes.as_mut_ptr()),
Some(&mut required_size),
);
update_subresources(
cmd,
destination_resource,
intermediate_resource,
first_subresouce,
num_subresources,
required_size,
&layouts,
&num_rows,
&row_sizes_in_bytes,
source,
)
}
}
#[allow(clippy::too_many_arguments)]
fn update_subresources(
cmd: &ID3D12GraphicsCommandList,
destination_resource: &ID3D12Resource,
intermediate_resource: &ID3D12Resource,
first_subresouce: u32,
num_subresources: u32,
required_size: u64,
layouts: &[D3D12_PLACED_SUBRESOURCE_FOOTPRINT],
num_rows: &[u32],
row_sizes_in_bytes: &[u64],
source_data: &[D3D12_SUBRESOURCE_DATA],
) -> anyhow::Result<u64> {
// ToDo: implement validation as in the original function
unsafe {
let mut data = std::ptr::null_mut();
intermediate_resource.Map(0, None, Some(&mut data))?;
for i in 0..num_subresources as usize {
let dest_data = D3D12_MEMCPY_DEST {
pData: data.offset(layouts[i].Offset as isize) as *mut std::ffi::c_void,
RowPitch: layouts[i].Footprint.RowPitch as usize,
SlicePitch: ((layouts[i].Footprint.RowPitch) * num_rows[i]) as usize,
};
memcpy_subresource(
&dest_data,
&source_data[i],
row_sizes_in_bytes[i],
num_rows[i],
layouts[i].Footprint.Depth,
);
}
intermediate_resource.Unmap(0, None);
}
unsafe {
let destination_desc = destination_resource.GetDesc();
if destination_desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER {
cmd.CopyBufferRegion(
destination_resource,
0,
intermediate_resource,
layouts[0].Offset,
layouts[0].Footprint.Width as u64,
);
} else {
for i in 0..num_subresources as usize {
let dest_location = D3D12_TEXTURE_COPY_LOCATION {
pResource: ManuallyDrop::new(Some(destination_resource.clone())),
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
SubresourceIndex: i as u32 + first_subresouce,
},
};
let source_location = D3D12_TEXTURE_COPY_LOCATION {
pResource: ManuallyDrop::new(Some(intermediate_resource.clone())),
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
PlacedFootprint: layouts[i],
},
};
cmd.CopyTextureRegion(&dest_location, 0, 0, 0, &source_location, None);
}
}
Ok(required_size)
}
}
// this function should not leak to the public API, so
// there is no point in using struct wrappers
unsafe fn memcpy_subresource(
dest: &D3D12_MEMCPY_DEST,
src: &D3D12_SUBRESOURCE_DATA,
row_sizes_in_bytes: u64,
num_rows: u32,
num_slices: u32,
) {
unsafe {
for z in 0..num_slices as usize {
let dest_slice = dest.pData.add(dest.SlicePitch * z);
let src_slice = src.pData.offset(src.SlicePitch * z as isize);
for y in 0..num_rows as usize {
std::ptr::copy_nonoverlapping(
src_slice.offset(src.RowPitch * y as isize),
dest_slice.add(dest.RowPitch * y),
row_sizes_in_bytes as usize,
);
}
}
}
}

View file

@ -0,0 +1,193 @@
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::d3d9::{FilterChain, FilterChainOptions, FrameOptions};
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8};
use std::path::Path;
use windows::Win32::Foundation::{HWND, TRUE};
use windows::Win32::Graphics::Direct3D9::{
Direct3DCreate9, IDirect3D9, IDirect3DDevice9, IDirect3DTexture9, D3DADAPTER_DEFAULT,
D3DCREATE_HARDWARE_VERTEXPROCESSING, D3DDEVTYPE_HAL, D3DFMT_A8R8G8B8, D3DLOCKED_RECT,
D3DPOOL_DEFAULT, D3DPOOL_MANAGED, D3DPOOL_SYSTEMMEM, D3DPRESENT_INTERVAL_IMMEDIATE,
D3DPRESENT_PARAMETERS, D3DSURFACE_DESC, D3DSWAPEFFECT_DISCARD, D3DUSAGE_RENDERTARGET,
D3D_SDK_VERSION,
};
pub struct Direct3D9 {
pub texture: IDirect3DTexture9,
pub image: Image<BGRA8>,
pub direct3d: IDirect3D9,
pub device: IDirect3DDevice9,
}
impl RenderTest for Direct3D9 {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
Direct3D9::new(path)
}
fn image_size(&self) -> Size<u32> {
self.image.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
unsafe {
let mut filter_chain = FilterChain::load_from_preset(
preset,
&self.device,
Some(&FilterChainOptions {
force_no_mipmaps: false,
disable_cache: false,
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let mut render_texture = None;
let output_size = output_size.unwrap_or(self.image.size);
self.device.CreateTexture(
output_size.width,
output_size.height,
1,
D3DUSAGE_RENDERTARGET as u32,
D3DFMT_A8R8G8B8,
D3DPOOL_DEFAULT,
&mut render_texture,
std::ptr::null_mut(),
)?;
let render_texture = render_texture
.ok_or_else(|| anyhow!("Unable to create Direct3D 9 render texture"))?;
let mut copy_texture = None;
self.device.CreateOffscreenPlainSurface(
output_size.width,
output_size.height,
D3DFMT_A8R8G8B8,
D3DPOOL_SYSTEMMEM,
&mut copy_texture,
std::ptr::null_mut(),
)?;
let copy_texture =
copy_texture.ok_or_else(|| anyhow!("Unable to create Direct3D 9 copy texture"))?;
let surface = render_texture.GetSurfaceLevel(0)?;
let options = frame_options.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
});
let viewport = Viewport::new_render_target_sized_origin(&surface, None)?;
for frame in 0..=frame_count {
filter_chain.frame(&self.texture, &viewport, frame, options.as_ref())?;
}
self.device.GetRenderTargetData(&surface, &copy_texture)?;
let mut desc = D3DSURFACE_DESC::default();
surface.GetDesc(&mut desc)?;
let mut lock = D3DLOCKED_RECT::default();
copy_texture.LockRect(&mut lock, std::ptr::null_mut(), 0)?;
let mut buffer = vec![0u8; desc.Height as usize * lock.Pitch as usize];
std::ptr::copy_nonoverlapping(lock.pBits.cast(), buffer.as_mut_ptr(), buffer.len());
copy_texture.UnlockRect()?;
BGRA8::convert(&mut buffer);
let image = RgbaImage::from_raw(output_size.width, output_size.height, buffer)
.ok_or(anyhow!("Unable to create image from data"))?;
Ok(image)
}
}
}
impl Direct3D9 {
pub fn new(image_path: impl AsRef<Path>) -> anyhow::Result<Self> {
let direct3d = unsafe {
Direct3DCreate9(D3D_SDK_VERSION)
.ok_or_else(|| anyhow!("Unable to create Direct3D 9 device"))?
};
let image = Image::<BGRA8>::load(image_path, UVDirection::TopLeft)?;
let mut present_params: D3DPRESENT_PARAMETERS = Default::default();
present_params.BackBufferWidth = image.size.width;
present_params.BackBufferHeight = image.size.height;
present_params.Windowed = TRUE;
present_params.SwapEffect = D3DSWAPEFFECT_DISCARD;
present_params.BackBufferFormat = D3DFMT_A8R8G8B8;
present_params.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE as u32;
let device = unsafe {
let mut device = None;
direct3d.CreateDevice(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
HWND(std::ptr::null_mut()),
D3DCREATE_HARDWARE_VERTEXPROCESSING as u32,
&mut present_params,
&mut device,
)?;
device.ok_or_else(|| anyhow!("Unable to create Direct3D 9 device"))?
};
let texture = unsafe {
let mut texture = None;
device.CreateTexture(
image.size.width,
image.size.height,
1,
0,
D3DFMT_A8R8G8B8,
D3DPOOL_MANAGED,
&mut texture,
std::ptr::null_mut(),
)?;
texture.ok_or_else(|| anyhow!("Unable to create Direct3D 9 texture"))?
};
unsafe {
let mut lock = D3DLOCKED_RECT::default();
texture.LockRect(0, &mut lock, std::ptr::null_mut(), 0)?;
std::ptr::copy_nonoverlapping(
image.bytes.as_ptr(),
lock.pBits.cast(),
image.bytes.len(),
);
texture.UnlockRect(0)?;
}
Ok(Self {
texture,
image,
direct3d,
device,
})
}
}

View file

@ -0,0 +1,67 @@
// Copyright (c) 2023 Christian Vallentin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use anyhow::anyhow;
use glfw::{fail_on_errors, Context, Glfw, OpenGlProfileHint, PWindow, WindowHint, WindowMode};
use std::sync::Arc;
pub struct GlfwContext {
_wnd: PWindow,
_glfw: Glfw,
pub gl: Arc<glow::Context>,
}
impl GlfwContext {
pub fn new(version: GLVersion, width: u32, height: u32) -> Result<Self, anyhow::Error> {
let mut glfw = glfw::init(fail_on_errors!())?;
let GLVersion(major, minor) = version;
glfw.window_hint(WindowHint::ContextVersion(major, minor));
glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
glfw.window_hint(WindowHint::Visible(false));
glfw.window_hint(WindowHint::OpenGlDebugContext(true));
let (mut wnd, _events) = glfw
.create_window(width, height, env!("CARGO_PKG_NAME"), WindowMode::Windowed)
.ok_or_else(|| anyhow!("No window"))?;
wnd.make_current();
let gl =
unsafe { glow::Context::from_loader_function(|proc| wnd.get_proc_address(proc) as _) };
// unsafe {
// gl.enable(glow::DEBUG_OUTPUT);
// gl.enable(glow::DEBUG_OUTPUT_SYNCHRONOUS);
// gl.debug_message_callback(debug_callback);
// gl.debug_message_control(glow::DONT_CARE, glow::DONT_CARE, glow::DONT_CARE, &[], true);
// }
Ok(Self {
_wnd: wnd,
_glfw: glfw,
gl: Arc::new(gl),
})
}
}
#[derive(Debug)]
pub struct GLVersion(pub u32, pub u32);

View file

@ -0,0 +1,259 @@
mod context;
use crate::render::gl::context::{GLVersion, GlfwContext};
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use glow::{HasContext, PixelPackData, PixelUnpackData};
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::gl::{FilterChain, FilterChainOptions, FrameOptions, GLImage};
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use librashader_runtime::image::{Image, UVDirection, RGBA8};
use std::path::Path;
use std::sync::Arc;
struct OpenGl {
context: GlfwContext,
texture: GLImage,
image_bytes: Image<RGBA8>,
}
pub struct OpenGl3(OpenGl);
pub struct OpenGl4(OpenGl);
impl RenderTest for OpenGl3 {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
OpenGl3::new(path)
}
fn image_size(&self) -> Size<u32> {
self.0.image_bytes.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
let mut filter_chain = unsafe {
FilterChain::load_from_preset(
preset,
Arc::clone(&self.0.context.gl),
Some(&FilterChainOptions {
glsl_version: 330,
use_dsa: false,
force_no_mipmaps: false,
disable_cache: false,
}),
)
}?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
Ok(self.0.render(
&mut filter_chain,
frame_count,
output_size,
frame_options
.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
})
.as_ref(),
)?)
}
}
impl RenderTest for OpenGl4 {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
OpenGl4::new(path)
}
fn image_size(&self) -> Size<u32> {
self.0.image_bytes.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
let mut filter_chain = unsafe {
FilterChain::load_from_preset(
preset,
Arc::clone(&self.0.context.gl),
Some(&FilterChainOptions {
glsl_version: 460,
use_dsa: true,
force_no_mipmaps: false,
disable_cache: true,
}),
)
}?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
Ok(self.0.render(
&mut filter_chain,
frame_count,
output_size,
frame_options
.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
})
.as_ref(),
)?)
}
}
impl OpenGl3 {
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
Ok(Self(OpenGl::new(image_path, false)?))
}
}
impl OpenGl4 {
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
Ok(Self(OpenGl::new(image_path, true)?))
}
}
impl OpenGl {
pub fn new(image_path: &Path, use_dsa: bool) -> anyhow::Result<Self> {
let image: Image<RGBA8> = Image::load(image_path, UVDirection::TopLeft)?;
let height = image.size.height;
let width = image.size.width;
let version = if use_dsa {
GLVersion(4, 6)
} else {
GLVersion(3, 3)
};
let context = GlfwContext::new(version, width, height)?;
let texture = unsafe {
let tex = context.gl.create_texture().map_err(|s| anyhow!("{}", s))?;
context.gl.bind_texture(glow::TEXTURE_2D, Some(tex));
context.gl.tex_storage_2d(
glow::TEXTURE_2D,
1,
glow::RGBA8,
image.size.width as i32,
image.size.height as i32,
);
context.gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, 0);
context.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 4);
context.gl.bind_buffer(glow::PIXEL_UNPACK_BUFFER, None);
context.gl.tex_sub_image_2d(
glow::TEXTURE_2D,
0,
0,
0,
image.size.width as i32,
image.size.height as i32,
glow::RGBA,
glow::UNSIGNED_BYTE,
PixelUnpackData::Slice(&image.bytes),
);
context.gl.bind_texture(glow::TEXTURE_2D, None);
tex
};
Ok(Self {
context,
texture: GLImage {
handle: Some(texture),
format: glow::RGBA8,
size: image.size,
},
image_bytes: image,
})
}
pub fn render(
&self,
chain: &mut FilterChain,
frame_count: usize,
output_size: Option<Size<u32>>,
options: Option<&FrameOptions>,
) -> Result<RgbaImage, anyhow::Error> {
let output_size = output_size.unwrap_or(self.image_bytes.size);
let render_texture = unsafe {
let tex = self
.context
.gl
.create_texture()
.map_err(|s| anyhow!("{}", s))?;
self.context.gl.bind_texture(glow::TEXTURE_2D, Some(tex));
self.context.gl.tex_storage_2d(
glow::TEXTURE_2D,
1,
glow::RGBA8,
output_size.width as i32,
output_size.height as i32,
);
self.context.gl.bind_texture(glow::TEXTURE_2D, None);
tex
};
let output = GLImage {
handle: Some(render_texture),
format: glow::RGBA8,
size: output_size,
};
let viewport = Viewport::new_render_target_sized_origin(&output, None)?;
for frame in 0..=frame_count {
unsafe {
chain.frame(&self.texture, &viewport, frame, options)?;
}
}
let mut data = vec![0u8; output_size.width as usize * output_size.height as usize * 4];
unsafe {
self.context
.gl
.bind_texture(glow::TEXTURE_2D, output.handle);
self.context.gl.get_tex_image(
glow::TEXTURE_2D,
0,
glow::RGBA,
glow::UNSIGNED_BYTE,
PixelPackData::Slice(&mut data),
)
}
Ok(
RgbaImage::from_raw(output_size.width, output_size.height, data)
.ok_or(anyhow!("failed to create image from slice"))?,
)
}
}

View file

@ -0,0 +1,174 @@
#[cfg(all(windows, feature = "d3d11"))]
pub mod d3d11;
#[cfg(all(windows, feature = "d3d12"))]
pub mod d3d12;
#[cfg(all(windows, feature = "d3d9"))]
pub mod d3d9;
#[cfg(feature = "opengl")]
pub mod gl;
#[cfg(feature = "vulkan")]
pub mod vk;
#[cfg(feature = "wgpu")]
pub mod wgpu;
#[cfg(all(target_vendor = "apple", feature = "metal"))]
pub mod mtl;
use librashader::presets::ShaderPreset;
use librashader::runtime::Size;
use librashader_runtime::impl_default_frame_options;
use librashader_runtime::parameters::RuntimeParameters;
use std::path::Path;
/// Test harness to set up a device, render a triangle, and apply a shader
pub trait RenderTest {
/// Create a new instance of the test harness.
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized;
/// Get the size of the image loaded.
fn image_size(&self) -> Size<u32>;
/// Render a shader onto an image buffer, applying the provided shader.
///
/// The test should render in linear colour space for proper comparison against
/// backends.
///
/// For testing purposes, it is often that a single image will be reused with multiple
/// shader presets, so the actual image that a shader will be applied to
/// will often be part of the test harness object.
fn render(
&mut self,
path: &Path,
frame_count: usize,
output_size: Option<Size<u32>>,
) -> anyhow::Result<image::RgbaImage> {
let preset = ShaderPreset::try_parse(path)?;
self.render_with_preset(preset, frame_count, output_size)
}
/// Render a shader onto an image buffer, applying the provided shader.
///
/// The test should render in linear colour space for proper comparison against
/// backends.
///
/// For testing purposes, it is often that a single image will be reused with multiple
/// shader presets, so the actual image that a shader will be applied to
/// will often be part of the test harness object.
fn render_with_preset(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
) -> anyhow::Result<image::RgbaImage> {
self.render_with_preset_and_params(preset, frame_count, output_size, None, None)
}
/// Render a shader onto an image buffer, applying the provided shader.
///
/// The test should render in linear colour space for proper comparison against
/// backends.
///
/// For testing purposes, it is often that a single image will be reused with multiple
/// shader presets, so the actual image that a shader will be applied to
/// will often be part of the test harness object.
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage>;
}
impl_default_frame_options!(CommonFrameOptions);
#[cfg(test)]
mod test {
use crate::render::RenderTest;
use image::codecs::png::PngEncoder;
use std::fs::File;
const IMAGE_PATH: &str = "../triangle.png";
const FILTER_PATH: &str = "../test/shaders_slang/crt/crt-royale.slangp";
// const FILTER_PATH: &str =
// "../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp";
fn do_test<T: RenderTest>() -> anyhow::Result<()> {
let mut test = T::new(IMAGE_PATH.as_ref())?;
let image = test.render(FILTER_PATH.as_ref(), 100)?;
let out = File::create("out.png")?;
image.write_with_encoder(PngEncoder::new(out))?;
Ok(())
}
#[test]
#[cfg(all(windows, feature = "d3d11"))]
pub fn test_d3d11() -> anyhow::Result<()> {
do_test::<crate::render::d3d11::Direct3D11>()
}
#[test]
#[cfg(feature = "wgpu")]
pub fn test_wgpu() -> anyhow::Result<()> {
do_test::<crate::render::wgpu::Wgpu>()
}
#[test]
#[cfg(feature = "vulkan")]
pub fn test_vk() -> anyhow::Result<()> {
do_test::<crate::render::vk::Vulkan>()
}
#[test]
#[cfg(feature = "opengl")]
pub fn test_gl3() -> anyhow::Result<()> {
do_test::<crate::render::gl::OpenGl3>()
}
#[test]
#[cfg(feature = "opengl")]
pub fn test_gl4() -> anyhow::Result<()> {
do_test::<crate::render::gl::OpenGl4>()
}
#[test]
#[cfg(all(target_vendor = "apple", feature = "metal"))]
pub fn test_metal() -> anyhow::Result<()> {
do_test::<crate::render::mtl::Metal>()
}
#[test]
#[cfg(all(windows, feature = "d3d9"))]
pub fn test_d3d9() -> anyhow::Result<()> {
do_test::<crate::render::d3d9::Direct3D9>()
}
#[test]
#[cfg(all(windows, feature = "d3d12"))]
pub fn test_d3d12() -> anyhow::Result<()> {
do_test::<crate::render::d3d12::Direct3D12>()
}
pub fn compare<A: RenderTest, B: RenderTest>() -> anyhow::Result<()> {
let mut a = A::new(IMAGE_PATH.as_ref())?;
let mut b = B::new(IMAGE_PATH.as_ref())?;
let a_image = a.render(FILTER_PATH.as_ref(), 100)?;
let b_image = b.render(FILTER_PATH.as_ref(), 100)?;
let similarity = image_compare::rgba_hybrid_compare(&a_image, &b_image)?;
assert!(similarity.score > 0.95);
Ok(())
}
}

View file

@ -0,0 +1,208 @@
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::mtl::{FilterChain, FilterChainOptions, FrameOptions};
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8};
use objc2::ffi::NSUInteger;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLCommandBuffer, MTLCommandQueue, MTLCreateSystemDefaultDevice, MTLDevice, MTLOrigin,
MTLPixelFormat, MTLRegion, MTLSize, MTLStorageMode, MTLTexture, MTLTextureDescriptor,
MTLTextureUsage,
};
use std::path::Path;
use std::ptr::NonNull;
pub struct Metal {
device: Retained<ProtocolObject<dyn MTLDevice>>,
texture: Retained<ProtocolObject<dyn MTLTexture>>,
image_bytes: Image<BGRA8>,
}
impl RenderTest for Metal {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
Metal::new(path)
}
fn image_size(&self) -> Size<u32> {
self.image_bytes.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
let queue = self
.device
.newCommandQueue()
.ok_or_else(|| anyhow!("Unable to create command queue"))?;
let cmd = queue
.commandBuffer()
.ok_or_else(|| anyhow!("Unable to create command buffer"))?;
let mut filter_chain = FilterChain::load_from_preset(
preset,
&queue,
Some(&FilterChainOptions {
force_no_mipmaps: false,
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let output_size = output_size.unwrap_or(self.image_bytes.size);
let render_texture = unsafe {
let texture_descriptor =
MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped(
MTLPixelFormat::BGRA8Unorm,
output_size.width as NSUInteger,
output_size.height as NSUInteger,
false,
);
texture_descriptor.setSampleCount(1);
texture_descriptor.setStorageMode(
if cfg!(all(target_arch = "aarch64", target_vendor = "apple")) {
MTLStorageMode::Shared
} else {
MTLStorageMode::Managed
},
);
texture_descriptor.setUsage(MTLTextureUsage::ShaderWrite);
let texture = self
.device
.newTextureWithDescriptor(&texture_descriptor)
.ok_or_else(|| anyhow!("Failed to create texture"))?;
texture
};
let viewport = Viewport::new_render_target_sized_origin(render_texture.as_ref(), None)?;
let options = frame_options.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
});
for frame in 0..=frame_count {
filter_chain.frame(
&self.texture,
&viewport,
cmd.as_ref(),
frame,
options.as_ref(),
)?;
}
cmd.commit();
unsafe {
cmd.waitUntilCompleted();
}
let region = MTLRegion {
origin: MTLOrigin { x: 0, y: 0, z: 0 },
size: MTLSize {
width: output_size.width as usize,
height: output_size.height as usize,
depth: 1,
},
};
unsafe {
// should be the same size
let mut buffer =
vec![0u8; output_size.width as usize * output_size.height as usize * 4];
render_texture.getBytes_bytesPerRow_fromRegion_mipmapLevel(
NonNull::new(buffer.as_mut_ptr().cast()).unwrap(),
4 * output_size.width as usize,
region,
0,
);
// swap the BGRA back to RGBA.
BGRA8::convert(&mut buffer);
let image = RgbaImage::from_raw(
render_texture.width() as u32,
render_texture.height() as u32,
Vec::from(buffer),
)
.ok_or(anyhow!("Unable to create image from data"))?;
Ok(image)
}
}
}
impl Metal {
pub fn new(image_path: impl AsRef<Path>) -> anyhow::Result<Self> {
let image: Image<BGRA8> = Image::load(image_path, UVDirection::TopLeft)?;
unsafe {
let device = Retained::from_raw(MTLCreateSystemDefaultDevice())
.ok_or_else(|| anyhow!("Unable to create default Metal device"))?;
let texture_descriptor =
MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped(
MTLPixelFormat::BGRA8Unorm,
image.size.width as NSUInteger,
image.size.height as NSUInteger,
false,
);
texture_descriptor.setSampleCount(1);
texture_descriptor.setStorageMode(
if cfg!(all(target_arch = "aarch64", target_vendor = "apple")) {
MTLStorageMode::Shared
} else {
MTLStorageMode::Managed
},
);
texture_descriptor.setUsage(MTLTextureUsage::ShaderRead);
let texture = device
.newTextureWithDescriptor(&texture_descriptor)
.ok_or_else(|| anyhow!("Failed to create texture"))?;
let region = MTLRegion {
origin: MTLOrigin { x: 0, y: 0, z: 0 },
size: MTLSize {
width: image.size.width as usize,
height: image.size.height as usize,
depth: 1,
},
};
texture.replaceRegion_mipmapLevel_withBytes_bytesPerRow(
region,
0,
// SAFETY: replaceRegion withBytes is const.
NonNull::new_unchecked(image.bytes.as_slice().as_ptr() as *mut _),
4 * image.size.width as usize,
);
Ok(Self {
device,
texture,
image_bytes: image,
})
}
}
}

View file

@ -0,0 +1,153 @@
use ash::vk;
use gpu_allocator::vulkan::Allocator;
use librashader::runtime::vk::VulkanObjects;
use parking_lot::Mutex;
use std::ffi::CStr;
use std::sync::Arc;
pub struct VulkanBase {
device: Arc<ash::Device>,
graphics_queue: vk::Queue,
allocator: Arc<Mutex<Allocator>>,
cmd_buffer: vk::CommandBuffer,
pool: vk::CommandPool,
}
impl From<&VulkanBase> for VulkanObjects {
fn from(value: &VulkanBase) -> Self {
VulkanObjects {
device: Arc::clone(&value.device),
alloc: Arc::clone(&value.allocator),
queue: value.graphics_queue.clone(),
}
}
}
const KHRONOS_VALIDATION: &[u8] = b"VK_LAYER_KHRONOS_validation\0";
const LIBRASHADER_VULKAN: &[u8] = b"librashader Vulkan\0";
impl VulkanBase {
pub fn new() -> anyhow::Result<Self> {
let entry = unsafe { ash::Entry::load() }?;
let layers = [KHRONOS_VALIDATION.as_ptr().cast()];
let app_info = vk::ApplicationInfo::default()
.application_name(unsafe { CStr::from_bytes_with_nul_unchecked(LIBRASHADER_VULKAN) })
.engine_name(unsafe { CStr::from_bytes_with_nul_unchecked(LIBRASHADER_VULKAN) })
.engine_version(0)
.application_version(0)
.api_version(vk::make_api_version(0, 1, 3, 0));
let create_info = vk::InstanceCreateInfo::default()
.application_info(&app_info)
.enabled_layer_names(&layers)
.enabled_extension_names(&[]);
let instance = unsafe { entry.create_instance(&create_info, None) }?;
let physical_device = super::physical_device::pick_physical_device(&instance)?;
let (device, queue, cmd_pool) = Self::create_device(&instance, &physical_device)?;
let alloc = super::memory::create_allocator(
device.clone(),
instance.clone(),
physical_device.clone(),
);
let buffer_info = vk::CommandBufferAllocateInfo::default()
.command_pool(cmd_pool)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(1);
let buffers = unsafe { device.allocate_command_buffers(&buffer_info) }?
.into_iter()
.next()
.unwrap();
Ok(Self {
device: Arc::new(device),
graphics_queue: queue,
// debug,
allocator: alloc,
pool: cmd_pool,
cmd_buffer: buffers,
})
}
pub(crate) fn device(&self) -> &Arc<ash::Device> {
&self.device
}
pub(crate) fn allocator(&self) -> &Arc<Mutex<Allocator>> {
&self.allocator
}
fn create_device(
instance: &ash::Instance,
physical_device: &vk::PhysicalDevice,
) -> anyhow::Result<(ash::Device, vk::Queue, vk::CommandPool)> {
let _debug = [unsafe { CStr::from_bytes_with_nul_unchecked(KHRONOS_VALIDATION).as_ptr() }];
let indices = super::physical_device::find_queue_family(&instance, *physical_device);
let queue_info = [vk::DeviceQueueCreateInfo::default()
.queue_family_index(indices.graphics_family()?)
.queue_priorities(&[1.0f32])];
let mut physical_device_features =
vk::PhysicalDeviceVulkan13Features::default().dynamic_rendering(true);
let extensions = [ash::khr::dynamic_rendering::NAME.as_ptr()];
let device_create_info = vk::DeviceCreateInfo::default()
.queue_create_infos(&queue_info)
.enabled_extension_names(&extensions)
.push_next(&mut physical_device_features);
let device =
unsafe { instance.create_device(*physical_device, &device_create_info, None)? };
let queue = unsafe { device.get_device_queue(indices.graphics_family()?, 0) };
let create_info = vk::CommandPoolCreateInfo::default()
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
.queue_family_index(indices.graphics_family()?);
let pool = unsafe { device.create_command_pool(&create_info, None) }?;
Ok((device, queue, pool))
}
/// Simple helper function to synchronously queue work on the graphics queue
pub fn queue_work<T>(&self, f: impl FnOnce(vk::CommandBuffer) -> T) -> anyhow::Result<T> {
unsafe {
self.device.begin_command_buffer(
self.cmd_buffer,
&vk::CommandBufferBeginInfo::default()
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
)?
}
let result = f(self.cmd_buffer);
unsafe {
self.device.end_command_buffer(self.cmd_buffer)?;
self.device.queue_submit(
self.graphics_queue,
&[vk::SubmitInfo::default().command_buffers(&[self.cmd_buffer])],
vk::Fence::null(),
)?;
self.device.queue_wait_idle(self.graphics_queue)?;
self.device
.reset_command_buffer(self.cmd_buffer, vk::CommandBufferResetFlags::empty())?;
}
Ok(result)
}
}
impl Drop for VulkanBase {
fn drop(&mut self) {
unsafe {
self.device.destroy_command_pool(self.pool, None);
}
}
}

View file

@ -0,0 +1,134 @@
use ash::vk;
use gpu_allocator::vulkan::{
Allocation, AllocationCreateDesc, AllocationScheme, Allocator, AllocatorCreateDesc,
};
use gpu_allocator::{AllocationSizes, MemoryLocation};
use parking_lot::Mutex;
use anyhow::anyhow;
use std::mem::ManuallyDrop;
use std::sync::Arc;
pub struct VulkanImageMemory {
pub(crate) allocation: ManuallyDrop<Allocation>,
allocator: Arc<Mutex<Allocator>>,
}
impl VulkanImageMemory {
pub fn new(
device: &Arc<ash::Device>,
allocator: &Arc<Mutex<Allocator>>,
requirements: vk::MemoryRequirements,
image: &vk::Image,
location: MemoryLocation,
) -> anyhow::Result<VulkanImageMemory> {
let allocation = allocator.lock().allocate(&AllocationCreateDesc {
name: "imagemem",
requirements,
location,
linear: false,
allocation_scheme: AllocationScheme::DedicatedImage(*image),
})?;
unsafe {
device.bind_image_memory(*image, allocation.memory(), 0)?;
Ok(VulkanImageMemory {
allocation: ManuallyDrop::new(allocation),
allocator: Arc::clone(allocator),
})
}
}
}
impl Drop for VulkanImageMemory {
fn drop(&mut self) {
let allocation = unsafe { ManuallyDrop::take(&mut self.allocation) };
if let Err(e) = self.allocator.lock().free(allocation) {
println!("librashader-runtime-vk: [warn] failed to deallocate image buffer {e}")
}
}
}
pub struct VulkanBuffer {
pub handle: vk::Buffer,
device: Arc<ash::Device>,
allocation: ManuallyDrop<Allocation>,
allocator: Arc<Mutex<Allocator>>,
}
impl VulkanBuffer {
pub fn new(
device: &Arc<ash::Device>,
allocator: &Arc<Mutex<Allocator>>,
usage: vk::BufferUsageFlags,
size: usize,
) -> anyhow::Result<VulkanBuffer> {
unsafe {
let buffer_info = vk::BufferCreateInfo::default()
.size(size as vk::DeviceSize)
.usage(usage)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let buffer = device.create_buffer(&buffer_info, None)?;
let memory_reqs = device.get_buffer_memory_requirements(buffer);
let alloc = allocator.lock().allocate(&AllocationCreateDesc {
name: "buffer",
requirements: memory_reqs,
location: MemoryLocation::CpuToGpu,
linear: true,
allocation_scheme: AllocationScheme::DedicatedBuffer(buffer),
})?;
// let alloc = device.allocate_memory(&alloc_info, None)?;
device.bind_buffer_memory(buffer, alloc.memory(), 0)?;
Ok(VulkanBuffer {
handle: buffer,
allocation: ManuallyDrop::new(alloc),
allocator: Arc::clone(allocator),
device: device.clone(),
})
}
}
pub fn as_mut_slice(&mut self) -> anyhow::Result<&mut [u8]> {
let Some(allocation) = self.allocation.mapped_slice_mut() else {
return Err(anyhow!("Allocation is not host visible"));
};
Ok(allocation)
}
}
impl Drop for VulkanBuffer {
fn drop(&mut self) {
unsafe {
// SAFETY: things can not be double dropped.
let allocation = ManuallyDrop::take(&mut self.allocation);
if let Err(e) = self.allocator.lock().free(allocation) {
println!("librashader-test-vk: [warn] failed to deallocate buffer memory {e}")
}
if self.handle != vk::Buffer::null() {
self.device.destroy_buffer(self.handle, None);
}
}
}
}
pub fn create_allocator(
device: ash::Device,
instance: ash::Instance,
physical_device: vk::PhysicalDevice,
) -> Arc<Mutex<Allocator>> {
let alloc = Allocator::new(&AllocatorCreateDesc {
instance,
device,
physical_device,
debug_settings: Default::default(),
buffer_device_address: false,
allocation_sizes: AllocationSizes::default(),
})
.unwrap();
Arc::new(Mutex::new(alloc))
}

View file

@ -0,0 +1,391 @@
use crate::render::vk::base::VulkanBase;
use crate::render::vk::memory::{VulkanBuffer, VulkanImageMemory};
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use ash::vk;
use gpu_allocator::MemoryLocation;
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::vk::{FilterChain, FilterChainOptions, FrameOptions, VulkanImage};
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use librashader_runtime::image::{Image, UVDirection, BGRA8};
use std::path::Path;
mod base;
mod memory;
mod physical_device;
mod util;
pub struct Vulkan {
vk: VulkanBase,
image_bytes: Image<BGRA8>,
image: vk::Image,
_image_alloc: VulkanImageMemory,
}
impl RenderTest for Vulkan {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
Vulkan::new(path)
}
fn image_size(&self) -> Size<u32> {
self.image_bytes.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
unsafe {
let mut filter_chain = FilterChain::load_from_preset(
preset,
&self.vk,
Some(&FilterChainOptions {
frames_in_flight: 3,
force_no_mipmaps: false,
use_dynamic_rendering: false,
disable_cache: false,
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let image_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::B8G8R8A8_UNORM)
.extent(output_size.map_or(self.image_bytes.size.into(), |size| size.into()))
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_SRC)
.initial_layout(vk::ImageLayout::UNDEFINED);
let render_texture = { self.vk.device().create_image(&image_info, None)? };
// This just needs to stay alive until the read.
let _memory = {
let mem_reqs = self
.vk
.device()
.get_image_memory_requirements(render_texture);
VulkanImageMemory::new(
self.vk.device(),
&self.vk.allocator(),
mem_reqs,
&render_texture,
MemoryLocation::GpuOnly,
)?
};
let transfer_texture = self.vk.device().create_image(
&vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::R8G8B8A8_UNORM)
.extent(self.image_bytes.size.into())
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::LINEAR)
.usage(vk::ImageUsageFlags::TRANSFER_DST)
.initial_layout(vk::ImageLayout::UNDEFINED),
None,
)?;
let mut transfer_memory = {
let mem_reqs = self
.vk
.device()
.get_image_memory_requirements(transfer_texture);
VulkanImageMemory::new(
self.vk.device(),
&self.vk.allocator(),
mem_reqs,
&transfer_texture,
MemoryLocation::GpuToCpu,
)?
};
self.vk.queue_work(|cmd| {
util::vulkan_image_layout_transition_levels(
&self.vk.device(),
cmd,
render_texture,
vk::REMAINING_MIP_LEVELS,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
vk::PipelineStageFlags::ALL_GRAPHICS,
vk::PipelineStageFlags::ALL_GRAPHICS,
vk::QUEUE_FAMILY_IGNORED,
vk::QUEUE_FAMILY_IGNORED,
);
util::vulkan_image_layout_transition_levels(
&self.vk.device(),
cmd,
transfer_texture,
vk::REMAINING_MIP_LEVELS,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::GENERAL,
vk::AccessFlags::TRANSFER_WRITE | vk::AccessFlags::TRANSFER_READ,
vk::AccessFlags::TRANSFER_WRITE | vk::AccessFlags::TRANSFER_READ,
vk::PipelineStageFlags::ALL_GRAPHICS,
vk::PipelineStageFlags::TRANSFER,
vk::QUEUE_FAMILY_IGNORED,
vk::QUEUE_FAMILY_IGNORED,
);
let options = frame_options.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
});
let viewport = Viewport::new_render_target_sized_origin(
VulkanImage {
image: render_texture,
size: self.image_bytes.size.into(),
format: vk::Format::B8G8R8A8_UNORM,
},
None,
)?;
for frame in 0..=frame_count {
filter_chain.frame(
&VulkanImage {
image: self.image,
size: self.image_bytes.size,
format: vk::Format::B8G8R8A8_UNORM,
},
&viewport,
cmd,
frame,
options.as_ref(),
)?;
}
{
util::vulkan_image_layout_transition_levels(
&self.vk.device(),
cmd,
render_texture,
vk::REMAINING_MIP_LEVELS,
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
vk::AccessFlags::TRANSFER_READ,
vk::PipelineStageFlags::ALL_GRAPHICS,
vk::PipelineStageFlags::TRANSFER,
vk::QUEUE_FAMILY_IGNORED,
vk::QUEUE_FAMILY_IGNORED,
)
}
let offsets = [
vk::Offset3D { x: 0, y: 0, z: 0 },
vk::Offset3D {
x: self.image_bytes.size.width as i32,
y: self.image_bytes.size.height as i32,
z: 1,
},
];
let subresource = vk::ImageSubresourceLayers::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_array_layer(0)
.layer_count(1);
let image_blit = vk::ImageBlit::default()
.src_subresource(subresource.clone())
.src_offsets(offsets.clone())
.dst_subresource(subresource)
.dst_offsets(offsets);
self.vk.device().cmd_blit_image(
cmd,
render_texture,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
transfer_texture,
vk::ImageLayout::GENERAL,
&[image_blit],
vk::Filter::NEAREST,
);
Ok::<_, anyhow::Error>(())
})??;
// should have read now.
let mut memory = transfer_memory
.allocation
.mapped_slice_mut()
.ok_or(anyhow!("readback buffer was not mapped"))?;
let layout = self.vk.device().get_image_subresource_layout(
transfer_texture,
vk::ImageSubresource::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.array_layer(0),
);
memory = &mut memory[layout.offset as usize..];
// let mut pixels = Vec::with_capacity(layout.size as usize);
let image = RgbaImage::from_raw(
self.image_bytes.size.width,
self.image_bytes.size.height,
Vec::from(memory),
)
.ok_or(anyhow!("failed to create image from slice"));
self.vk.device().destroy_image(transfer_texture, None);
self.vk.device().destroy_image(render_texture, None);
Ok(image?)
}
}
}
impl Vulkan {
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
let vk = VulkanBase::new()?;
let (image_bytes, image_alloc, image, _view) = Self::load_image(&vk, image_path)?;
Ok(Self {
vk,
image,
image_bytes,
_image_alloc: image_alloc,
})
}
pub fn load_image(
vk: &VulkanBase,
image_path: &Path,
) -> anyhow::Result<(Image<BGRA8>, VulkanImageMemory, vk::Image, vk::ImageView)> {
let image: Image<BGRA8> = Image::load(image_path, UVDirection::TopLeft)?;
let image_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::B8G8R8A8_UNORM)
.extent(image.size.into())
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(
vk::ImageUsageFlags::SAMPLED
| vk::ImageUsageFlags::TRANSFER_SRC
| vk::ImageUsageFlags::TRANSFER_DST,
)
.initial_layout(vk::ImageLayout::UNDEFINED);
let texture = unsafe { vk.device().create_image(&image_info, None)? };
let memory = unsafe {
let mem_reqs = vk.device().get_image_memory_requirements(texture);
VulkanImageMemory::new(
vk.device(),
&vk.allocator(),
mem_reqs,
&texture,
MemoryLocation::GpuOnly,
)?
};
let image_subresource = vk::ImageSubresourceRange::default()
.level_count(image_info.mip_levels)
.layer_count(1)
.aspect_mask(vk::ImageAspectFlags::COLOR);
let swizzle_components = vk::ComponentMapping::default()
.r(vk::ComponentSwizzle::R)
.g(vk::ComponentSwizzle::G)
.b(vk::ComponentSwizzle::B)
.a(vk::ComponentSwizzle::A);
let view_info = vk::ImageViewCreateInfo::default()
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk::Format::B8G8R8A8_UNORM)
.image(texture)
.subresource_range(image_subresource)
.components(swizzle_components);
let texture_view = unsafe { vk.device().create_image_view(&view_info, None)? };
let mut staging = VulkanBuffer::new(
&vk.device(),
&vk.allocator(),
vk::BufferUsageFlags::TRANSFER_SRC,
image.bytes.len(),
)?;
staging.as_mut_slice()?.copy_from_slice(&image.bytes);
vk.queue_work(|cmd| unsafe {
util::vulkan_image_layout_transition_levels(
&vk.device(),
cmd,
texture,
vk::REMAINING_MIP_LEVELS,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::AccessFlags::empty(),
vk::AccessFlags::TRANSFER_WRITE,
vk::PipelineStageFlags::TOP_OF_PIPE,
vk::PipelineStageFlags::TRANSFER,
vk::QUEUE_FAMILY_IGNORED,
vk::QUEUE_FAMILY_IGNORED,
);
let builder = vk::BufferImageCopy::default()
.image_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.mip_level(0)
.base_array_layer(0)
.layer_count(1),
)
.image_extent(image.size.into());
vk.device().cmd_copy_buffer_to_image(
cmd,
staging.handle,
texture,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
&[builder],
);
util::vulkan_image_layout_transition_levels(
&vk.device(),
cmd,
texture,
vk::REMAINING_MIP_LEVELS,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::AccessFlags::TRANSFER_WRITE,
vk::AccessFlags::SHADER_READ,
vk::PipelineStageFlags::TRANSFER,
vk::PipelineStageFlags::ALL_GRAPHICS,
vk::QUEUE_FAMILY_IGNORED,
vk::QUEUE_FAMILY_IGNORED,
);
})?;
Ok((image, memory, texture, texture_view))
}
}

View file

@ -0,0 +1,144 @@
use anyhow::anyhow;
use ash::vk;
use std::ffi::CStr;
pub struct QueueFamilyIndices {
graphics_family: Option<u32>,
}
impl QueueFamilyIndices {
pub fn is_complete(&self) -> bool {
self.graphics_family.is_some()
}
pub fn graphics_family(&self) -> anyhow::Result<u32> {
self.graphics_family
.ok_or(anyhow!("The queue family is not complete"))
}
}
pub(crate) fn pick_physical_device(instance: &ash::Instance) -> anyhow::Result<vk::PhysicalDevice> {
let physical_devices = unsafe { instance.enumerate_physical_devices()? };
let mut result = None;
for &physical_device in physical_devices.iter() {
if is_physical_device_suitable(instance, physical_device) && result.is_none() {
result = Some(physical_device)
}
}
result.ok_or(anyhow!("Failed to find a suitable GPU"))
}
fn is_physical_device_suitable(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
) -> bool {
let device_properties = unsafe { instance.get_physical_device_properties(physical_device) };
let device_features = unsafe { instance.get_physical_device_features(physical_device) };
let device_queue_families =
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
if cfg!(feature = "vulkan-debug") {
let device_type = match device_properties.device_type {
vk::PhysicalDeviceType::CPU => "Cpu",
vk::PhysicalDeviceType::INTEGRATED_GPU => "Integrated GPU",
vk::PhysicalDeviceType::DISCRETE_GPU => "Discrete GPU",
vk::PhysicalDeviceType::VIRTUAL_GPU => "Virtual GPU",
vk::PhysicalDeviceType::OTHER => "Unknown",
_ => panic!(),
};
let device_name = unsafe { CStr::from_ptr(device_properties.device_name.as_ptr()) };
let device_name = device_name.to_string_lossy();
println!(
"\tDevice Name: {}, id: {}, type: {}",
device_name, device_properties.device_id, device_type
);
println!("\tAPI Version: {}", device_properties.api_version);
println!("\tSupport Queue Family: {}", device_queue_families.len());
println!("\t\tQueue Count | Graphics, Compute, Transfer, Sparse Binding");
for queue_family in device_queue_families.iter() {
let is_graphics_support = if queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS)
{
"support"
} else {
"unsupport"
};
let is_compute_support = if queue_family.queue_flags.contains(vk::QueueFlags::COMPUTE) {
"support"
} else {
"unsupport"
};
let is_transfer_support = if queue_family.queue_flags.contains(vk::QueueFlags::TRANSFER)
{
"support"
} else {
"unsupport"
};
let is_sparse_support = if queue_family
.queue_flags
.contains(vk::QueueFlags::SPARSE_BINDING)
{
"support"
} else {
"unsupport"
};
println!(
"\t\t{}\t | {}, {}, {}, {}",
queue_family.queue_count,
is_graphics_support,
is_compute_support,
is_transfer_support,
is_sparse_support
);
}
// there are plenty of features
println!(
"\tGeometry Shader support: {}",
if device_features.geometry_shader == 1 {
"support"
} else {
"unsupported"
}
);
}
let indices = find_queue_family(instance, physical_device);
indices.is_complete()
}
pub fn find_queue_family(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
) -> QueueFamilyIndices {
let queue_families =
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
let mut queue_family_indices = QueueFamilyIndices {
graphics_family: None,
};
let mut index = 0;
for queue_family in queue_families.iter() {
if queue_family.queue_count > 0
&& queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS)
{
queue_family_indices.graphics_family = Some(index);
}
if queue_family_indices.is_complete() {
break;
}
index += 1;
}
queue_family_indices
}

View file

@ -0,0 +1,42 @@
use ash::vk;
#[inline(always)]
pub unsafe fn vulkan_image_layout_transition_levels(
device: &ash::Device,
cmd: vk::CommandBuffer,
image: vk::Image,
levels: u32,
old_layout: vk::ImageLayout,
new_layout: vk::ImageLayout,
src_access: vk::AccessFlags,
dst_access: vk::AccessFlags,
src_stage: vk::PipelineStageFlags,
dst_stage: vk::PipelineStageFlags,
src_queue_family_index: u32,
dst_queue_family_index: u32,
) {
let mut barrier = vk::ImageMemoryBarrier::default();
barrier.s_type = vk::StructureType::IMAGE_MEMORY_BARRIER;
barrier.p_next = std::ptr::null();
barrier.src_access_mask = src_access;
barrier.dst_access_mask = dst_access;
barrier.old_layout = old_layout;
barrier.new_layout = new_layout;
barrier.src_queue_family_index = src_queue_family_index;
barrier.dst_queue_family_index = dst_queue_family_index;
barrier.image = image;
barrier.subresource_range.aspect_mask = vk::ImageAspectFlags::COLOR;
barrier.subresource_range.base_array_layer = 0;
barrier.subresource_range.level_count = levels;
barrier.subresource_range.layer_count = vk::REMAINING_ARRAY_LAYERS;
device.cmd_pipeline_barrier(
cmd,
src_stage,
dst_stage,
vk::DependencyFlags::empty(),
&[],
&[],
&[barrier],
)
}

View file

@ -0,0 +1,268 @@
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use image::RgbaImage;
use librashader::runtime::wgpu::*;
use librashader::runtime::{Size, Viewport};
use librashader_runtime::image::{Image, UVDirection};
use std::io::{Cursor, Write};
use std::ops::DerefMut;
use std::path::Path;
use std::sync::Arc;
use wgpu::{Adapter, Device, Instance, Queue, Texture};
use wgpu_types::{
BufferAddress, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, ImageCopyBuffer,
ImageDataLayout, Maintain, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
};
use librashader::presets::ShaderPreset;
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use parking_lot::Mutex;
pub struct Wgpu {
_instance: Instance,
_adapter: Adapter,
device: Arc<Device>,
queue: Arc<Queue>,
image: Image,
texture: Arc<Texture>,
}
struct BufferDimensions {
height: usize,
unpadded_bytes_per_row: usize,
padded_bytes_per_row: usize,
}
impl BufferDimensions {
fn new(width: usize, height: usize) -> Self {
let bytes_per_pixel = std::mem::size_of::<u32>();
let unpadded_bytes_per_row = width * bytes_per_pixel;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
Self {
height,
unpadded_bytes_per_row,
padded_bytes_per_row,
}
}
}
impl RenderTest for Wgpu {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
Wgpu::new(path)
}
fn image_size(&self) -> Size<u32> {
self.image.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
let mut chain = FilterChain::load_from_preset(
preset,
Arc::clone(&self.device),
Arc::clone(&self.queue),
Some(&FilterChainOptions {
force_no_mipmaps: false,
enable_cache: true,
adapter_info: None,
}),
)?;
if let Some(setter) = param_setter {
setter(&chain.parameters());
}
let mut cmd = self
.device
.create_command_encoder(&CommandEncoderDescriptor { label: None });
let output_tex = self.device.create_texture(&TextureDescriptor {
label: None,
size: output_size.map_or(self.texture.size(), |size| size.into()),
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8Unorm,
usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC,
view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
});
let buffer_dimensions =
BufferDimensions::new(output_tex.width() as usize, output_tex.height() as usize);
let output_buf = Arc::new(self.device.create_buffer(&BufferDescriptor {
label: None,
size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height)
as BufferAddress, // 4bpp
usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
mapped_at_creation: false,
}));
let view = output_tex.create_view(&wgpu::TextureViewDescriptor::default());
let output = WgpuOutputView::new_from_raw(
&view,
output_tex.size().into(),
TextureFormat::Rgba8Unorm,
);
let viewport = Viewport::new_render_target_sized_origin(output, None)?;
let options = frame_options.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
});
for frame in 0..=frame_count {
chain.frame(
Arc::clone(&self.texture),
&viewport,
&mut cmd,
frame,
options.as_ref(),
)?;
}
cmd.copy_texture_to_buffer(
output_tex.as_image_copy(),
ImageCopyBuffer {
buffer: &output_buf,
layout: ImageDataLayout {
offset: 0,
bytes_per_row: Some(buffer_dimensions.padded_bytes_per_row as u32),
rows_per_image: None,
},
},
output_tex.size(),
);
let si = self.queue.submit([cmd.finish()]);
self.device.poll(Maintain::WaitForSubmissionIndex(si));
let capturable = Arc::clone(&output_buf);
let pixels = Arc::new(Mutex::new(Vec::new()));
let pixels_async = Arc::clone(&pixels);
output_buf
.slice(..)
.map_async(wgpu::MapMode::Read, move |r| {
if r.is_ok() {
let buffer = capturable.slice(..).get_mapped_range();
let mut pixels = pixels_async.lock();
pixels.resize(buffer.len(), 0);
let mut cursor = Cursor::new(pixels.deref_mut());
for chunk in buffer.chunks(buffer_dimensions.padded_bytes_per_row) {
cursor
.write_all(&chunk[..buffer_dimensions.unpadded_bytes_per_row])
.unwrap()
}
cursor.into_inner();
}
capturable.unmap();
});
self.device.poll(Maintain::Wait);
if pixels.lock().len() == 0 {
return Err(anyhow!("failed to copy pixels from buffer"));
}
let image = RgbaImage::from_raw(
output_tex.width(),
output_tex.height(),
pixels.lock().to_vec(),
)
.ok_or(anyhow!("Unable to create image from data"))?;
Ok(image)
}
}
impl Wgpu {
pub fn new(image: &Path) -> anyhow::Result<Self> {
pollster::block_on(async {
let instance = wgpu::Instance::default();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: None,
force_fallback_adapter: false,
})
.await
.ok_or(anyhow!("Couldn't request WGPU adapter"))?;
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
required_features: wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER
| wgpu::Features::PIPELINE_CACHE
| wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
| wgpu::Features::FLOAT32_FILTERABLE,
required_limits: wgpu::Limits::default(),
label: None,
memory_hints: Default::default(),
},
None,
)
.await?;
let (image, texture) = Self::load_image(&device, &queue, image)?;
Ok(Self {
_instance: instance,
_adapter: adapter,
device: Arc::new(device),
queue: Arc::new(queue),
image: image,
texture: Arc::new(texture),
})
})
}
fn load_image(device: &Device, queue: &Queue, path: &Path) -> anyhow::Result<(Image, Texture)> {
let image = Image::load(path, UVDirection::TopLeft)?;
let texture = device.create_texture(&TextureDescriptor {
size: image.size.into(),
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
format: wgpu::TextureFormat::Rgba8Unorm,
view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
label: None,
});
queue.write_texture(
wgpu::ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&image.bytes,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * image.size.width),
rows_per_image: None,
},
image.size.into(),
);
let si = queue.submit([]);
device.poll(Maintain::WaitForSubmissionIndex(si));
Ok((image, texture))
}
}

View file

@ -3,7 +3,7 @@ name = "librashader-common"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.3.0"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -13,22 +13,26 @@ description = "RetroArch shaders for all."
[features]
default = []
opengl = ["gl"]
opengl = ["glow"]
d3d9 = ["windows"]
d3d11 = ["windows", "dxgi"]
d3d12 = ["windows", "dxgi"]
dxgi = ["windows"]
vulkan = ["ash"]
wgpu = ["wgpu-types"]
metal = ["objc2-metal"]
metal = ["objc2", "objc2-metal"]
serde = ["dep:serde", "serde/derive", "smartstring/serde", "halfbrown/serde"]
[dependencies]
gl = { version = "0.14.0", optional = true }
ash = { version = "0.38", optional = true }
wgpu-types = { version = "22", optional = true }
num-traits = "0.2.15"
rustc-hash = "2.0.0"
halfbrown = "0.2.4"
smartstring = "1.0"
glow = { workspace = true, optional = true }
ash = { workspace = true, optional = true }
wgpu-types = { workspace = true, optional = true }
serde = { version = "1.0", optional = true }
[target.'cfg(windows)'.dependencies.windows]
optional = true
@ -42,7 +46,14 @@ features = [
"Win32_Graphics_Direct3D12",
]
[target.'cfg(target_vendor="apple")'.dependencies.objc2]
optional = true
workspace = true
features = ["apple"]
[target.'cfg(target_vendor="apple")'.dependencies.objc2-metal]
optional = true
version = "0.2.0"
workspace = true
features = ["MTLPixelFormat", "MTLRenderCommandEncoder", "MTLSampler"]

View file

@ -1,5 +1,7 @@
use crate::{FilterMode, WrapMode};
use crate::{FilterMode, GetSize, Size, WrapMode};
use windows::Win32::Foundation::E_NOINTERFACE;
use windows::Win32::Graphics::Direct3D11;
use windows::Win32::Graphics::Direct3D11::{ID3D11Texture2D, D3D11_RESOURCE_DIMENSION_TEXTURE2D};
impl From<WrapMode> for Direct3D11::D3D11_TEXTURE_ADDRESS_MODE {
fn from(value: WrapMode) -> Self {
@ -20,3 +22,82 @@ impl From<FilterMode> for Direct3D11::D3D11_FILTER {
}
}
}
impl GetSize<u32> for &Direct3D11::ID3D11RenderTargetView {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let parent: ID3D11Texture2D = unsafe {
let resource = self.GetResource()?;
if resource.GetType() != D3D11_RESOURCE_DIMENSION_TEXTURE2D {
return Err(windows::core::Error::new(
E_NOINTERFACE,
"expected ID3D11Texture2D as the resource for the view.",
));
}
// SAFETY: We know tha the resource is an `ID3D11Texture2D`.
// This downcast is safe because ID3D11Texture2D has ID3D11Resource in its
// inheritance chain.
//
// This check + transmute is cheaper than doing `.cast` (i.e. `QueryInterface`).
std::mem::transmute(resource)
};
let mut desc = Default::default();
unsafe {
parent.GetDesc(&mut desc);
}
Ok(Size {
height: desc.Height,
width: desc.Width,
})
}
}
impl GetSize<u32> for Direct3D11::ID3D11RenderTargetView {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
<&Self as GetSize<u32>>::size(&self)
}
}
impl GetSize<u32> for &Direct3D11::ID3D11ShaderResourceView {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let parent: ID3D11Texture2D = unsafe {
let resource = self.GetResource()?;
if resource.GetType() != D3D11_RESOURCE_DIMENSION_TEXTURE2D {
return Err(windows::core::Error::new(
E_NOINTERFACE,
"expected ID3D11Texture2D as the resource for the view.",
));
}
// SAFETY: We know tha the resource is an `ID3D11Texture2D`.
// This downcast is safe because ID3D11Texture2D has ID3D11Resource in its
// inheritance chain.
//
// This check + transmute is cheaper than doing `.cast` (i.e. `QueryInterface`).
std::mem::transmute(resource)
};
let mut desc = Default::default();
unsafe {
parent.GetDesc(&mut desc);
}
Ok(Size {
height: desc.Height,
width: desc.Width,
})
}
}
impl GetSize<u32> for Direct3D11::ID3D11ShaderResourceView {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
<&Self as GetSize<u32>>::size(&self)
}
}

View file

@ -1,4 +1,4 @@
use crate::{FilterMode, WrapMode};
use crate::{FilterMode, GetSize, Size, WrapMode};
use windows::Win32::Graphics::Direct3D12;
impl From<WrapMode> for Direct3D12::D3D12_TEXTURE_ADDRESS_MODE {
@ -20,3 +20,12 @@ impl From<FilterMode> for Direct3D12::D3D12_FILTER {
}
}
}
impl GetSize<u32> for Direct3D12::ID3D12Resource {
type Error = std::convert::Infallible;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let desc = unsafe { self.GetDesc() };
Ok(Size::new(desc.Width as u32, desc.Height))
}
}

View file

@ -1,4 +1,4 @@
use crate::{FilterMode, ImageFormat, WrapMode};
use crate::{FilterMode, GetSize, ImageFormat, Size, WrapMode};
use windows::Win32::Graphics::Direct3D9;
//
impl From<ImageFormat> for Direct3D9::D3DFORMAT {
@ -64,6 +64,52 @@ impl From<FilterMode> for Direct3D9::D3DTEXTUREFILTER {
}
}
impl GetSize<u32> for &Direct3D9::IDirect3DSurface9 {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let mut desc = Default::default();
unsafe {
self.GetDesc(&mut desc)?;
}
Ok(Size {
height: desc.Height,
width: desc.Width,
})
}
}
impl GetSize<u32> for Direct3D9::IDirect3DSurface9 {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
<&Self as GetSize<u32>>::size(&self)
}
}
impl GetSize<u32> for &Direct3D9::IDirect3DTexture9 {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let mut desc = Default::default();
unsafe {
self.GetLevelDesc(0, &mut desc)?;
}
Ok(Size {
height: desc.Height,
width: desc.Width,
})
}
}
impl GetSize<u32> for Direct3D9::IDirect3DTexture9 {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
<&Self as GetSize<u32>>::size(&self)
}
}
// impl FilterMode {
// /// Get the mipmap filtering mode for the given combination.
// pub fn d3d9_mip(&self, mip: FilterMode) -> Direct3D9::D3DTEXTUREFILTER {

View file

@ -1,71 +1,71 @@
use crate::{FilterMode, ImageFormat, WrapMode};
impl From<ImageFormat> for gl::types::GLenum {
impl From<ImageFormat> for u32 {
fn from(format: ImageFormat) -> Self {
match format {
ImageFormat::Unknown => 0 as gl::types::GLenum,
ImageFormat::R8Unorm => gl::R8,
ImageFormat::R8Uint => gl::R8UI,
ImageFormat::R8Sint => gl::R8I,
ImageFormat::R8G8Unorm => gl::RG8,
ImageFormat::R8G8Uint => gl::RG8UI,
ImageFormat::R8G8Sint => gl::RG8I,
ImageFormat::R8G8B8A8Unorm => gl::RGBA8,
ImageFormat::R8G8B8A8Uint => gl::RGBA8UI,
ImageFormat::R8G8B8A8Sint => gl::RGBA8I,
ImageFormat::R8G8B8A8Srgb => gl::SRGB8_ALPHA8,
ImageFormat::A2B10G10R10UnormPack32 => gl::RGB10_A2,
ImageFormat::A2B10G10R10UintPack32 => gl::RGB10_A2UI,
ImageFormat::R16Uint => gl::R16UI,
ImageFormat::R16Sint => gl::R16I,
ImageFormat::R16Sfloat => gl::R16F,
ImageFormat::R16G16Uint => gl::RG16UI,
ImageFormat::R16G16Sint => gl::RG16I,
ImageFormat::R16G16Sfloat => gl::RG16F,
ImageFormat::R16G16B16A16Uint => gl::RGBA16UI,
ImageFormat::R16G16B16A16Sint => gl::RGBA16I,
ImageFormat::R16G16B16A16Sfloat => gl::RGBA16F,
ImageFormat::R32Uint => gl::R32UI,
ImageFormat::R32Sint => gl::R32I,
ImageFormat::R32Sfloat => gl::R32F,
ImageFormat::R32G32Uint => gl::RG32UI,
ImageFormat::R32G32Sint => gl::RG32I,
ImageFormat::R32G32Sfloat => gl::RG32F,
ImageFormat::R32G32B32A32Uint => gl::RGBA32UI,
ImageFormat::R32G32B32A32Sint => gl::RGBA32I,
ImageFormat::R32G32B32A32Sfloat => gl::RGBA32F,
ImageFormat::Unknown => 0,
ImageFormat::R8Unorm => glow::R8,
ImageFormat::R8Uint => glow::R8UI,
ImageFormat::R8Sint => glow::R8I,
ImageFormat::R8G8Unorm => glow::RG8,
ImageFormat::R8G8Uint => glow::RG8UI,
ImageFormat::R8G8Sint => glow::RG8I,
ImageFormat::R8G8B8A8Unorm => glow::RGBA8,
ImageFormat::R8G8B8A8Uint => glow::RGBA8UI,
ImageFormat::R8G8B8A8Sint => glow::RGBA8I,
ImageFormat::R8G8B8A8Srgb => glow::SRGB8_ALPHA8,
ImageFormat::A2B10G10R10UnormPack32 => glow::RGB10_A2,
ImageFormat::A2B10G10R10UintPack32 => glow::RGB10_A2UI,
ImageFormat::R16Uint => glow::R16UI,
ImageFormat::R16Sint => glow::R16I,
ImageFormat::R16Sfloat => glow::R16F,
ImageFormat::R16G16Uint => glow::RG16UI,
ImageFormat::R16G16Sint => glow::RG16I,
ImageFormat::R16G16Sfloat => glow::RG16F,
ImageFormat::R16G16B16A16Uint => glow::RGBA16UI,
ImageFormat::R16G16B16A16Sint => glow::RGBA16I,
ImageFormat::R16G16B16A16Sfloat => glow::RGBA16F,
ImageFormat::R32Uint => glow::R32UI,
ImageFormat::R32Sint => glow::R32I,
ImageFormat::R32Sfloat => glow::R32F,
ImageFormat::R32G32Uint => glow::RG32UI,
ImageFormat::R32G32Sint => glow::RG32I,
ImageFormat::R32G32Sfloat => glow::RG32F,
ImageFormat::R32G32B32A32Uint => glow::RGBA32UI,
ImageFormat::R32G32B32A32Sint => glow::RGBA32I,
ImageFormat::R32G32B32A32Sfloat => glow::RGBA32F,
}
}
}
impl From<WrapMode> for gl::types::GLenum {
impl From<WrapMode> for i32 {
fn from(value: WrapMode) -> Self {
match value {
WrapMode::ClampToBorder => gl::CLAMP_TO_BORDER,
WrapMode::ClampToEdge => gl::CLAMP_TO_EDGE,
WrapMode::Repeat => gl::REPEAT,
WrapMode::MirroredRepeat => gl::MIRRORED_REPEAT,
WrapMode::ClampToBorder => glow::CLAMP_TO_BORDER as i32,
WrapMode::ClampToEdge => glow::CLAMP_TO_EDGE as i32,
WrapMode::Repeat => glow::REPEAT as i32,
WrapMode::MirroredRepeat => glow::MIRRORED_REPEAT as i32,
}
}
}
impl From<FilterMode> for gl::types::GLenum {
impl From<FilterMode> for i32 {
fn from(value: FilterMode) -> Self {
match value {
FilterMode::Linear => gl::LINEAR,
_ => gl::NEAREST,
FilterMode::Linear => glow::LINEAR as i32,
_ => glow::NEAREST as i32,
}
}
}
impl FilterMode {
/// Get the mipmap filtering mode for the given combination.
pub fn gl_mip(&self, mip: FilterMode) -> gl::types::GLenum {
pub fn gl_mip(&self, mip: FilterMode) -> u32 {
match (self, mip) {
(FilterMode::Linear, FilterMode::Linear) => gl::LINEAR_MIPMAP_LINEAR,
(FilterMode::Linear, FilterMode::Nearest) => gl::LINEAR_MIPMAP_NEAREST,
(FilterMode::Nearest, FilterMode::Linear) => gl::NEAREST_MIPMAP_LINEAR,
_ => gl::NEAREST_MIPMAP_NEAREST,
(FilterMode::Linear, FilterMode::Linear) => glow::LINEAR_MIPMAP_LINEAR,
(FilterMode::Linear, FilterMode::Nearest) => glow::LINEAR_MIPMAP_NEAREST,
(FilterMode::Nearest, FilterMode::Linear) => glow::NEAREST_MIPMAP_LINEAR,
_ => glow::NEAREST_MIPMAP_NEAREST,
}
}
}

View file

@ -38,18 +38,22 @@ pub mod map;
pub use viewport::Viewport;
use num_traits::AsPrimitive;
use num_traits::{AsPrimitive, Num};
use std::convert::Infallible;
use std::ops::{Add, Sub};
use std::str::FromStr;
#[derive(Debug, Clone)]
pub enum ShaderStorage {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum StorageType {
/// The fully qualified path to the resource, often a shader source file or a texture.
Path(std::path::PathBuf),
String(String),
}
#[repr(u32)]
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Supported image formats for textures.
pub enum ImageFormat {
#[default]
@ -96,6 +100,7 @@ pub enum ImageFormat {
#[repr(i32)]
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// The filtering mode for a texture sampler.
pub enum FilterMode {
/// Linear filtering.
@ -134,6 +139,7 @@ impl FromStr for FilterMode {
#[repr(i32)]
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// The wrapping (address) mode for a texture sampler.
pub enum WrapMode {
#[default]
@ -194,6 +200,7 @@ impl FromStr for ImageFormat {
/// A size with a width and height.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Size<T> {
pub width: T,
pub height: T,
@ -206,6 +213,50 @@ impl<T> Size<T> {
}
}
impl<T: Sub<Output = T>> Sub for Size<T> {
type Output = Size<T>;
fn sub(self, rhs: Self) -> Self::Output {
Self {
width: self.width - rhs.width,
height: self.height - rhs.height,
}
}
}
impl<T: Sub<T, Output = T> + Copy> Sub<T> for Size<T> {
type Output = Size<T>;
fn sub(self, rhs: T) -> Self::Output {
Self {
width: self.width - rhs,
height: self.height - rhs,
}
}
}
impl<T: Add<Output = T>> Add for Size<T> {
type Output = Size<T>;
fn add(self, rhs: Self) -> Self::Output {
Self {
width: self.width + rhs.width,
height: self.height + rhs.height,
}
}
}
impl<T: Add<T, Output = T> + Copy> Add<T> for Size<T> {
type Output = Size<T>;
fn add(self, rhs: T) -> Self::Output {
Self {
width: self.width + rhs,
height: self.height + rhs,
}
}
}
impl<T> From<Size<T>> for [f32; 4]
where
T: Copy + AsPrimitive<f32>,
@ -220,3 +271,22 @@ where
]
}
}
/// Trait for surface or texture objects that can fetch size.
pub trait GetSize<C: Num> {
type Error;
/// Fetch the size of the object
fn size(&self) -> Result<Size<C>, Self::Error>;
}
impl<T: GetSize<u32>> GetSize<f32> for T {
type Error = T::Error;
fn size(&self) -> Result<Size<f32>, Self::Error> {
let size = <T as GetSize<u32>>::size(self)?;
Ok(Size {
width: size.width as f32,
height: size.height as f32,
})
}
}

View file

@ -1,5 +1,8 @@
/// Fast optimized hash map type for small sets.
/// A hashmap optimized for small sets of size less than 32 with a fast hash implementation.
///
/// Used widely for shader reflection.
pub type FastHashMap<K, V> =
halfbrown::SizedHashMap<K, V, core::hash::BuildHasherDefault<rustc_hash::FxHasher>, 32>;
pub use halfbrown;
/// A string with small string optimizations up to 23 bytes.
pub type ShortString = smartstring::SmartString<smartstring::LazyCompact>;

View file

@ -1,6 +1,8 @@
use crate::{FilterMode, ImageFormat, Size, WrapMode};
use crate::{FilterMode, GetSize, ImageFormat, Size, WrapMode};
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLPixelFormat, MTLSamplerAddressMode, MTLSamplerMinMagFilter, MTLSamplerMipFilter, MTLViewport,
MTLPixelFormat, MTLSamplerAddressMode, MTLSamplerMinMagFilter, MTLSamplerMipFilter, MTLTexture,
MTLViewport,
};
impl From<ImageFormat> for MTLPixelFormat {
@ -92,3 +94,29 @@ impl FilterMode {
}
}
}
impl GetSize<u32> for ProtocolObject<dyn MTLTexture> {
type Error = std::convert::Infallible;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let height = self.height();
let width = self.width();
Ok(Size {
height: height as u32,
width: width as u32,
})
}
}
impl GetSize<u32> for &ProtocolObject<dyn MTLTexture> {
type Error = std::convert::Infallible;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let height = self.height();
let width = self.width();
Ok(Size {
height: height as u32,
width: width as u32,
})
}
}

View file

@ -1,6 +1,16 @@
use crate::{GetSize, Size};
/// The rendering output of a filter chain.
///
/// Viewport coordinates are relative to the coordinate system of the
/// target runtime. For correct results, `x` and `y` should almost always be
/// 0, and `size` should be the same as the size of the output texture.
///
/// Size uniforms will always be passed the full size of the output texture,
/// regardless of the user-specified viewport size.
pub struct Viewport<'a, T> {
/// The x offset to start rendering from.
/// The x offset to start rendering from. For correct results, this should almost
/// always be 0 to indicate the origin.
pub x: f32,
/// The y offset to begin rendering from.
pub y: f32,
@ -9,4 +19,28 @@ pub struct Viewport<'a, T> {
pub mvp: Option<&'a [f32; 16]>,
/// The output handle to render the final image to.
pub output: T,
/// The extent of the viewport size starting from the origin defined
/// by x and y.
pub size: Size<u32>,
}
impl<'a, T: GetSize<u32>> Viewport<'a, T> {
/// Create a new viewport from an output that can be sized.
///
/// This will create a viewport that spans the entire output texture,
/// which will give correct results in the general case.
#[inline(always)]
pub fn new_render_target_sized_origin(
output: T,
mvp: Option<&'a [f32; 16]>,
) -> Result<Viewport<'a, T>, T::Error> {
let size = output.size()?;
Ok(Self {
x: 0.0,
y: 0.0,
mvp,
output,
size,
})
}
}

View file

@ -0,0 +1,41 @@
[package]
name = "librashader-pack"
version = "0.5.1"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
categories = ["emulators", "compilers", "graphics"]
keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[dependencies]
librashader-presets = { path = "../librashader-presets", version = "0.5.1", features = [
"serde",
] }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.5.1", features = [
"serde",
] }
librashader-common = { path = "../librashader-common", version = "0.5.1", features = [
"serde",
] }
thiserror = "1.0.64"
serde = { version = "1.0", features = ["derive"], optional = true }
rayon = { workspace = true }
image = { workspace = true }
base64 = { version = "0.22.1", optional = true }
serde_bytes = { version = "0.11.15", optional = true }
[features]
parse_legacy_glsl = ["librashader-presets/parse_legacy_glsl"]
serde = ["dep:serde", "dep:base64", "dep:serde_bytes"]
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
rayon = { workspace = true }
[dev-dependencies]
anyhow = "1.0.89"
serde_json = "1.0.128"
rmp-serde = "1.3.0"

254
librashader-pack/src/lib.rs Normal file
View file

@ -0,0 +1,254 @@
//! Shader preset resource handling for librashader.
//!
//! This crate contains facilities to load shader preset resources from a [`ShaderPreset`].
//!
//! Also defines abstractly the `.slangpack` shader format implemented via serde derives on [`ShaderPresetPack`].
//!
use image::{ImageError, RgbaImage};
use librashader_common::StorageType;
use librashader_preprocess::{PreprocessError, ShaderSource};
use librashader_presets::{ParameterMeta, PassMeta, ShaderPreset, TextureMeta};
#[cfg(not(target_arch = "wasm32"))]
use rayon::prelude::*;
/// A buffer holding RGBA image bytes.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TextureBuffer {
#[cfg_attr(feature = "serde", serde(with = "serde_base64_or_bytes"))]
image: Vec<u8>,
width: u32,
height: u32,
}
impl From<TextureBuffer> for Option<RgbaImage> {
fn from(value: TextureBuffer) -> Self {
RgbaImage::from_raw(value.width, value.height, value.image)
}
}
impl AsRef<[u8]> for TextureBuffer {
fn as_ref(&self) -> &[u8] {
self.image.as_ref()
}
}
impl From<RgbaImage> for TextureBuffer {
fn from(value: RgbaImage) -> Self {
let width = value.width();
let height = value.height();
TextureBuffer {
image: value.into_raw(),
width,
height,
}
}
}
/// A resource for a shader preset, fully loaded into memory.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LoadedResource<M: LoadableResource> {
/// The fully qualified path to the texture.
pub data: M::ResourceType,
/// Meta information about the texture.
pub meta: M,
}
/// Trait for a resource that is loadable from disk.
pub trait LoadableResource {
/// The type of the resource.
type ResourceType;
/// The error type when loading the resource.
type Error;
/// Load the resource from the path.
fn load(storage: &StorageType) -> Result<Self::ResourceType, Self::Error>;
}
impl LoadableResource for PassMeta {
type ResourceType = ShaderSource;
type Error = PreprocessError;
fn load(storage: &StorageType) -> Result<Self::ResourceType, Self::Error> {
ShaderSource::load(storage)
}
}
impl LoadableResource for TextureMeta {
type ResourceType = TextureBuffer;
type Error = ImageError;
fn load(storage: &StorageType) -> Result<Self::ResourceType, Self::Error> {
let image = match storage {
StorageType::Path(path_buf) => image::open(path_buf),
StorageType::String(_) => todo!(),
};
image.map(|img| TextureBuffer::from(img.to_rgba8()))
}
}
/// The loaded resource information for the source code of a shader pass.
pub type PassResource = LoadedResource<PassMeta>;
/// The loaded texture resource for a shader preset.
pub type TextureResource = LoadedResource<TextureMeta>;
/// A fully loaded-in-memory shader preset, with all paths resolved to data.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShaderPresetPack {
/// Used in legacy GLSL shader semantics. If < 0, no feedback pass is used.
/// Otherwise, the FBO after pass #N is passed a texture to next frame
#[cfg(feature = "parse_legacy_glsl")]
pub feedback_pass: i32,
/// The number of shaders enabled in the filter chain.
pub pass_count: i32,
// Everything is in Vecs because the expect number of values is well below 64.
/// Preset information for each shader.
pub passes: Vec<PassResource>,
/// Preset information for each texture.
pub textures: Vec<TextureResource>,
/// Preset information for each user parameter.
pub parameters: Vec<ParameterMeta>,
}
impl ShaderPresetPack {
/// Load a `ShaderPack` from a [`ShaderPreset`].
pub fn load_from_preset<E>(preset: ShaderPreset) -> Result<ShaderPresetPack, E>
where
E: From<PreprocessError>,
E: From<ImageError>,
E: Send,
{
#[cfg(not(target_arch = "wasm32"))]
let shaders_iter = preset.passes.into_par_iter();
#[cfg(target_arch = "wasm32")]
let shaders_iter = preset.shaders.into_iter();
#[cfg(not(target_arch = "wasm32"))]
let textures_iter = preset.textures.into_par_iter();
#[cfg(target_arch = "wasm32")]
let textures_iter = preset.textures.into_iter();
Ok(ShaderPresetPack {
#[cfg(feature = "parse_legacy_glsl")]
feedback_pass: preset.feedback_pass,
pass_count: preset.pass_count,
passes: shaders_iter
.map(|v| {
Ok::<_, E>(PassResource {
data: PassMeta::load(&v.storage)?,
meta: v.meta,
})
})
.collect::<Result<Vec<_>, _>>()?,
textures: textures_iter
.into_par_iter()
.map(|t| {
Ok::<_, E>(TextureResource {
data: TextureMeta::load(&t.storage)?,
meta: t.meta,
})
})
.collect::<Result<Vec<_>, _>>()?,
parameters: preset.parameters,
})
}
}
#[cfg(feature = "serde")]
mod serde_base64_or_bytes {
use base64::display::Base64Display;
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use serde::{Deserializer, Serializer};
#[allow(clippy::ptr_arg)]
pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
if s.is_human_readable() {
s.collect_str(&Base64Display::new(v, &STANDARD))
} else {
serde_bytes::serialize(v, s)
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
if d.is_human_readable() {
struct Base64Visitor;
impl<'de> serde::de::Visitor<'de> for Base64Visitor {
type Value = Vec<u8>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a base64 encoded string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_bytes(v.as_ref())
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_bytes(v.as_ref())
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
STANDARD.decode(v).map_err(serde::de::Error::custom)
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_bytes(v.as_ref())
}
}
d.deserialize_str(Base64Visitor)
} else {
serde_bytes::deserialize(d)
}
}
}
#[cfg(test)]
mod test {
use crate::ShaderPresetPack;
use librashader_presets::ShaderPreset;
use std::fs::File;
use std::io::Write;
#[test]
fn test() {
let preset =
ShaderPreset::try_parse("../test/shaders_slang/crt/crt-royale.slangp").unwrap();
let resolved = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset).unwrap();
let mut file = File::create("crt-royale.slangpack.json").unwrap();
file.write_all(serde_json::to_vec_pretty(&resolved).unwrap().as_ref())
.unwrap();
}
#[test]
fn test_rmp() {
let preset =
ShaderPreset::try_parse("../test/shaders_slang/crt/crt-royale.slangp").unwrap();
let resolved = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset).unwrap();
let mut file = File::create("crt-royale.slangpack").unwrap();
file.write_all(rmp_serde::to_vec(&resolved).unwrap().as_ref())
.unwrap();
}
}

View file

@ -3,7 +3,7 @@ name = "librashader-preprocess"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.3.0"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -14,12 +14,14 @@ description = "RetroArch shaders for all."
[dependencies]
thiserror = "1.0.37"
nom = "7.1.1"
librashader-common = { path = "../librashader-common", version = "0.3.0" }
librashader-common = { path = "../librashader-common", version = "0.5.1" }
encoding_rs = "0.8.31"
serde = { version = "1.0", optional = true }
[features]
default = [ "line_directives" ]
line_directives = []
serde = ["dep:serde", "serde/derive", "librashader-common/serde"]
[dev-dependencies]
glob = "0.3.1"

View file

@ -1,3 +1,4 @@
use librashader_common::map::ShortString;
use std::convert::Infallible;
use std::path::PathBuf;
use thiserror::Error;
@ -27,7 +28,7 @@ pub enum PreprocessError {
PragmaParseError(String),
/// The given pragma was declared multiple times with differing values.
#[error("duplicate pragma found")]
DuplicatePragmaError(String),
DuplicatePragmaError(ShortString),
/// The image format requested by the shader was unknown or not supported.
#[error("shader format is unknown or not found")]
UnknownImageFormat,

View file

@ -2,9 +2,9 @@
//!
//! This crate contains facilities and types for resolving `#include` directives in `.slang`
//! into a single compilation unit. `#pragma` directives are also parsed and resolved as
//! [`ShaderParameter`](crate::ShaderParameter) structs.
//! [`ShaderParameter`] structs.
//!
//! The resulting [`ShaderSource`](crate::ShaderSource) can then be passed into a
//! The resulting [`ShaderSource`]can then be passed into a
//! reflection target for reflection and compilation into the target shader format.
//!
//! Re-exported as [`librashader::preprocess`](https://docs.rs/librashader/latest/librashader/preprocess/index.html).
@ -15,11 +15,13 @@ mod stage;
use crate::include::read_source;
pub use error::*;
use librashader_common::map::FastHashMap;
use librashader_common::ImageFormat;
use librashader_common::map::{FastHashMap, ShortString};
use librashader_common::{ImageFormat, StorageType};
use std::path::Path;
/// The source file for a single shader pass.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShaderSource {
/// The source contents for the vertex shader.
pub vertex: String,
@ -28,10 +30,10 @@ pub struct ShaderSource {
pub fragment: String,
/// The alias of the shader if available.
pub name: Option<String>,
pub name: Option<ShortString>,
/// The list of shader parameters found in the shader source.
pub parameters: FastHashMap<String, ShaderParameter>,
pub parameters: FastHashMap<ShortString, ShaderParameter>,
/// The image format the shader expects.
pub format: ImageFormat,
@ -39,9 +41,10 @@ pub struct ShaderSource {
/// A user tweakable parameter for the shader as declared in source.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShaderParameter {
/// The name of the parameter.
pub id: String,
pub id: ShortString,
/// The description of the parameter.
pub description: String,
/// The initial value the parameter is set to.
@ -57,8 +60,11 @@ pub struct ShaderParameter {
impl ShaderSource {
/// Load the source file at the given path, resolving includes relative to the location of the
/// source file.
pub fn load(file: &librashader_common::ShaderStorage) -> Result<ShaderSource, PreprocessError> {
load_shader_source(file)
pub fn load(storage: &StorageType) -> Result<ShaderSource, PreprocessError> {
match storage {
StorageType::Path(path_buf) => load_shader_source(path_buf),
StorageType::String(source) => parse_shader_source(source.clone()),
}
}
}
@ -77,15 +83,14 @@ impl SourceOutput for String {
}
}
pub(crate) fn load_shader_source(
file: &librashader_common::ShaderStorage,
) -> Result<ShaderSource, PreprocessError> {
let source = match file {
librashader_common::ShaderStorage::Path(path) => read_source(path)?,
librashader_common::ShaderStorage::String(s) => s.to_string(),
};
pub(crate) fn load_shader_source(path: impl AsRef<Path>) -> Result<ShaderSource, PreprocessError> {
let source = read_source(path)?;
parse_shader_source(source)
}
pub(crate) fn parse_shader_source(source: String) -> Result<ShaderSource, PreprocessError> {
let meta = pragma::parse_pragma_meta(&source)?;
let text = stage::process_stages(&source)?;
let parameters = FastHashMap::from_iter(meta.parameters.into_iter().map(|p| (p.id.clone(), p)));
@ -105,9 +110,9 @@ mod test {
#[test]
pub fn load_file() {
let result = load_shader_source(&librashader_common::ShaderStorage::Path(
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang".into(),
))
let result = load_shader_source(
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang",
)
.unwrap();
eprintln!("{:#}", result.vertex)
}

View file

@ -2,7 +2,9 @@ use crate::{PreprocessError, ShaderParameter};
use librashader_common::ImageFormat;
use nom::bytes::complete::{is_not, tag, take_while};
use nom::character::complete::multispace1;
use librashader_common::map::ShortString;
use nom::character::complete::{multispace0, multispace1};
use nom::combinator::opt;
use nom::number::complete::float;
use nom::sequence::delimited;
use nom::IResult;
@ -12,7 +14,7 @@ use std::str::FromStr;
pub(crate) struct ShaderMeta {
pub(crate) format: ImageFormat,
pub(crate) parameters: Vec<ShaderParameter>,
pub(crate) name: Option<String>,
pub(crate) name: Option<ShortString>,
}
fn parse_parameter_string(input: &str) -> Result<ShaderParameter, PreprocessError> {
@ -35,17 +37,25 @@ fn parse_parameter_string(input: &str) -> Result<ShaderParameter, PreprocessErro
let (input, minimum) = float(input)?;
let (input, _) = multispace1(input)?;
let (input, maximum) = float(input)?;
let (input, _) = multispace1(input)?;
let (input, step) = float(input)?;
// Step is actually optional and defaults to 0.02
// This behaviour can be seen in shaders like
// crt/crt-slangtest-cubic.slangp
// which doesn't have a step argument
// #pragma parameter OUT_GAMMA "Monitor Output Gamma" 2.2 1.8 2.4
// https://github.com/libretro/slang-shaders/blob/0e2939787076e4a8a83be89175557fde23abe837/crt/shaders/crt-slangtest/parameters.inc#L1
let (input, _) = multispace0(input)?;
let (input, step) = opt(float)(input)?;
Ok((
input,
ShaderParameter {
id: name.to_string(),
id: name.into(),
description: description.to_string(),
initial,
minimum,
maximum,
step,
step: step.unwrap_or(0.02),
},
))
}
@ -60,7 +70,7 @@ fn parse_parameter_string(input: &str) -> Result<ShaderParameter, PreprocessErro
Ok(param)
} else {
Ok(ShaderParameter {
id: name.to_string(),
id: name.into(),
description: description.to_string(),
initial: 0f32,
minimum: 0f32,
@ -89,7 +99,7 @@ pub(crate) fn parse_pragma_meta(source: impl AsRef<str>) -> Result<ShaderMeta, P
if let Some(format_string) = line.strip_prefix("#pragma format ") {
if format != ImageFormat::Unknown {
return Err(PreprocessError::DuplicatePragmaError(line.to_string()));
return Err(PreprocessError::DuplicatePragmaError(line.into()));
}
let format_string = format_string.trim();
@ -100,12 +110,12 @@ pub(crate) fn parse_pragma_meta(source: impl AsRef<str>) -> Result<ShaderMeta, P
}
}
if line.starts_with("#pragma name ") {
if let Some(pragma_name) = line.strip_prefix("#pragma name ") {
if name.is_some() {
return Err(PreprocessError::DuplicatePragmaError(line.to_string()));
return Err(PreprocessError::DuplicatePragmaError(line.into()));
}
name = Some(line.trim().to_string())
name = Some(ShortString::from(pragma_name.trim()))
}
}
@ -124,7 +134,7 @@ mod test {
#[test]
fn parses_parameter_pragma() {
assert_eq!(ShaderParameter {
id: "exc".to_string(),
id: "exc".into(),
description: "orizontal correction hack (games where players stay at center)".to_string(),
initial: 0.0,
minimum: -10.0,
@ -136,7 +146,7 @@ mod test {
#[test]
fn parses_parameter_pragma_test() {
assert_eq!(ShaderParameter {
id: "HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR".to_string(),
id: "HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR".into(),
description: " Scanline Dir Multiplier".to_string(),
initial: 100.0,
minimum: 25.0,
@ -144,4 +154,22 @@ mod test {
step: 25.0
}, parse_parameter_string(r#"#pragma parameter HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR " Scanline Dir Multiplier" 100 25 1600 25"#).unwrap())
}
#[test]
fn parses_parameter_pragma_with_no_step() {
assert_eq!(
ShaderParameter {
id: "OUT_GAMMA".into(),
description: "Monitor Output Gamma".to_string(),
initial: 2.2,
minimum: 1.8,
maximum: 2.4,
step: 0.02
},
parse_parameter_string(
r#"#pragma parameter OUT_GAMMA "Monitor Output Gamma" 2.2 1.8 2.4"#
)
.unwrap()
)
}
}

View file

@ -3,7 +3,7 @@ name = "librashader-presets"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.3.0"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -15,17 +15,18 @@ description = "RetroArch shaders for all."
thiserror = "1.0.37"
nom = "7.1.1"
nom_locate = "4.0.0"
librashader-common = { path = "../librashader-common", version = "0.3.0" }
librashader-common = { path = "../librashader-common", version = "0.5.1" }
num-traits = "0.2"
once_cell = "1"
# we don't need unicode
regex = { version = "1", default-features = false, features = ["perf"] }
vec_extract_if_polyfill = "0.1.0"
rustversion = "1.0"
os_str_bytes = { version = "6", features = ["conversions"] }
serde = { version = "1.0", optional = true }
[features]
parse_legacy_glsl = []
serde = ["dep:serde", "serde/derive", "librashader-common/serde"]
[dev-dependencies]
glob = "0.3.1"

View file

@ -207,6 +207,7 @@ impl ContextItem {
}
}
/// The wildcard key associated with the context item.
pub fn key(&self) -> &str {
match self {
ContextItem::ContentDirectory(_) => "CONTENT-DIR",
@ -330,7 +331,7 @@ impl WildcardContext {
///
/// This is a one way conversion, and will normalize rotation context items
/// into `VID-FINAL-ROT`.
pub fn to_hashmap(mut self) -> FastHashMap<String, String> {
pub fn into_hashmap(mut self) -> FastHashMap<String, String> {
let mut map = FastHashMap::default();
let last_user_rot = self
.0
@ -364,7 +365,6 @@ impl WildcardContext {
}
}
#[rustversion::since(1.74)]
pub(crate) fn apply_context(path: &mut PathBuf, context: &FastHashMap<String, String>) {
use std::ffi::{OsStr, OsString};
@ -411,53 +411,3 @@ pub(crate) fn apply_context(path: &mut PathBuf, context: &FastHashMap<String, St
*path = new_path;
}
}
#[rustversion::before(1.74)]
pub(crate) fn apply_context(path: &mut PathBuf, context: &FastHashMap<String, String>) {
use os_str_bytes::RawOsStr;
static WILDCARD_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\$([A-Z-_]+)\\$").unwrap());
if context.is_empty() {
return;
}
let path_str = RawOsStr::new(path.as_os_str());
let path_bytes = path_str.to_raw_bytes();
// Don't want to do any extra work if there's no match.
if !WILDCARD_REGEX.is_match(&path_bytes) {
return;
}
let mut new_path = PathBuf::with_capacity(path.capacity());
for component in path.components() {
match component {
Component::Normal(path) => {
let haystack = RawOsStr::new(path);
let haystack = haystack.to_raw_bytes();
let replaced =
WILDCARD_REGEX.replace_all(&haystack, |caps: &regex::bytes::Captures| {
let Some(name) = caps.get(1) else {
return caps[0].to_vec();
};
let Ok(key) = std::str::from_utf8(name.as_bytes()) else {
return caps[0].to_vec();
};
if let Some(replacement) = context.get(key) {
return RawOsStr::from_str(replacement).to_raw_bytes().to_vec();
}
return caps[0].to_vec();
});
// SAFETY: The original source is valid encoded bytes, and our replacement is
// valid encoded bytes. This upholds the safety requirements of `from_encoded_bytes_unchecked`.
new_path.push(RawOsStr::assert_cow_from_raw_bytes(&replaced.as_ref()).to_os_str())
}
_ => new_path.push(component),
}
}
// If no wildcards are found within the path, or the path after replacing the wildcards does not exist on disk, the path returned will be unaffected.
if let Ok(true) = new_path.try_exists() {
*path = new_path;
}
}

View file

@ -1,312 +0,0 @@
//! This is a stable polyfill for [`Vec::extract_if`](https://github.com/rust-lang/rust/issues/43244).
use core::ptr;
use core::slice;
/// Polyfill trait for [`Vec::extract_if`](https://github.com/rust-lang/rust/issues/43244).
pub(crate) trait MakeExtractIf<T> {
/// Creates an iterator which uses a closure to determine if an element should be removed.
///
/// If the closure returns true, then the element is removed and yielded.
/// If the closure returns false, the element will remain in the vector and will not be yielded
/// by the iterator.
///
/// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating
/// or the iteration short-circuits, then the remaining elements will be retained.
///
/// Note that `extract_if` also lets you mutate every element in the filter closure,
/// regardless of whether you choose to keep or remove it.
///
/// # Examples
///
/// Splitting an array into evens and odds, reusing the original allocation:
///
/// ```
/// use vec_extract_if_polyfill::MakeExtractIf;
/// let mut numbers = vec![1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15];
///
/// let evens = numbers.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
/// let odds = numbers;
///
/// assert_eq!(evens, vec![2, 4, 6, 8, 14]);
/// assert_eq!(odds, vec![1, 3, 5, 9, 11, 13, 15]);
/// ```
fn extract_if<F>(&mut self, filter: F) -> ExtractIf<T, F>
where
F: FnMut(&mut T) -> bool;
}
impl<T> MakeExtractIf<T> for Vec<T> {
fn extract_if<F>(&mut self, filter: F) -> ExtractIf<T, F>
where
F: FnMut(&mut T) -> bool,
{
let old_len = self.len();
// Guard against us getting leaked (leak amplification)
unsafe {
self.set_len(0);
}
ExtractIf {
vec: self,
idx: 0,
del: 0,
old_len,
pred: filter,
}
}
}
/// An iterator which uses a closure to determine if an element should be removed.
///
/// This struct is created by [`Vec::extract_if`].
/// See its documentation for more.
///
/// # Example
///
/// ```
/// use vec_extract_if_polyfill::MakeExtractIf;
///
/// let mut v = vec![0, 1, 2];
/// let iter = v.extract_if(|x| *x % 2 == 0);
/// ```
#[derive(Debug)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct ExtractIf<'a, T, F>
where
F: FnMut(&mut T) -> bool,
{
vec: &'a mut Vec<T>,
/// The index of the item that will be inspected by the next call to `next`.
idx: usize,
/// The number of items that have been drained (removed) thus far.
del: usize,
/// The original length of `vec` prior to draining.
old_len: usize,
/// The filter test predicate.
pred: F,
}
impl<T, F> Iterator for ExtractIf<'_, T, F>
where
F: FnMut(&mut T) -> bool,
{
type Item = T;
fn next(&mut self) -> Option<T> {
unsafe {
while self.idx < self.old_len {
let i = self.idx;
let v = slice::from_raw_parts_mut(self.vec.as_mut_ptr(), self.old_len);
let drained = (self.pred)(&mut v[i]);
// Update the index *after* the predicate is called. If the index
// is updated prior and the predicate panics, the element at this
// index would be leaked.
self.idx += 1;
if drained {
self.del += 1;
return Some(ptr::read(&v[i]));
} else if self.del > 0 {
let del = self.del;
let src: *const T = &v[i];
let dst: *mut T = &mut v[i - del];
ptr::copy_nonoverlapping(src, dst, 1);
}
}
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(self.old_len - self.idx))
}
}
impl<T, F> Drop for ExtractIf<'_, T, F>
where
F: FnMut(&mut T) -> bool,
{
fn drop(&mut self) {
unsafe {
if self.idx < self.old_len && self.del > 0 {
// This is a pretty messed up state, and there isn't really an
// obviously right thing to do. We don't want to keep trying
// to execute `pred`, so we just backshift all the unprocessed
// elements and tell the vec that they still exist. The backshift
// is required to prevent a double-drop of the last successfully
// drained item prior to a panic in the predicate.
let ptr = self.vec.as_mut_ptr();
let src = ptr.add(self.idx);
let dst = src.sub(self.del);
let tail_len = self.old_len - self.idx;
src.copy_to(dst, tail_len);
}
self.vec.set_len(self.old_len - self.del);
}
}
}
#[cfg(test)]
mod test {
use crate::extract_if::MakeExtractIf;
#[test]
fn drain_filter_empty() {
let mut vec: Vec<i32> = vec![];
{
let mut iter = vec.extract_if(|_| true);
assert_eq!(iter.size_hint(), (0, Some(0)));
assert_eq!(iter.next(), None);
assert_eq!(iter.size_hint(), (0, Some(0)));
assert_eq!(iter.next(), None);
assert_eq!(iter.size_hint(), (0, Some(0)));
}
assert_eq!(vec.len(), 0);
assert_eq!(vec, vec![]);
}
#[test]
fn drain_filter_zst() {
let mut vec = vec![(), (), (), (), ()];
let initial_len = vec.len();
let mut count = 0;
{
let mut iter = vec.extract_if(|_| true);
assert_eq!(iter.size_hint(), (0, Some(initial_len)));
while let Some(_) = iter.next() {
count += 1;
assert_eq!(iter.size_hint(), (0, Some(initial_len - count)));
}
assert_eq!(iter.size_hint(), (0, Some(0)));
assert_eq!(iter.next(), None);
assert_eq!(iter.size_hint(), (0, Some(0)));
}
assert_eq!(count, initial_len);
assert_eq!(vec.len(), 0);
assert_eq!(vec, vec![]);
}
#[test]
fn drain_filter_false() {
let mut vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let initial_len = vec.len();
let mut count = 0;
{
let mut iter = vec.extract_if(|_| false);
assert_eq!(iter.size_hint(), (0, Some(initial_len)));
for _ in iter.by_ref() {
count += 1;
}
assert_eq!(iter.size_hint(), (0, Some(0)));
assert_eq!(iter.next(), None);
assert_eq!(iter.size_hint(), (0, Some(0)));
}
assert_eq!(count, 0);
assert_eq!(vec.len(), initial_len);
assert_eq!(vec, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
}
#[test]
fn drain_filter_true() {
let mut vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let initial_len = vec.len();
let mut count = 0;
{
let mut iter = vec.extract_if(|_| true);
assert_eq!(iter.size_hint(), (0, Some(initial_len)));
while let Some(_) = iter.next() {
count += 1;
assert_eq!(iter.size_hint(), (0, Some(initial_len - count)));
}
assert_eq!(iter.size_hint(), (0, Some(0)));
assert_eq!(iter.next(), None);
assert_eq!(iter.size_hint(), (0, Some(0)));
}
assert_eq!(count, initial_len);
assert_eq!(vec.len(), 0);
assert_eq!(vec, vec![]);
}
#[test]
fn drain_filter_complex() {
{
// [+xxx++++++xxxxx++++x+x++]
let mut vec = vec![
1, 2, 4, 6, 7, 9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 27, 29, 31, 33, 34, 35, 36,
37, 39,
];
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
assert_eq!(removed.len(), 10);
assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]);
assert_eq!(vec.len(), 14);
assert_eq!(
vec,
vec![1, 7, 9, 11, 13, 15, 17, 27, 29, 31, 33, 35, 37, 39]
);
}
{
// [xxx++++++xxxxx++++x+x++]
let mut vec = vec![
2, 4, 6, 7, 9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 27, 29, 31, 33, 34, 35, 36, 37,
39,
];
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
assert_eq!(removed.len(), 10);
assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]);
assert_eq!(vec.len(), 13);
assert_eq!(vec, vec![7, 9, 11, 13, 15, 17, 27, 29, 31, 33, 35, 37, 39]);
}
{
// [xxx++++++xxxxx++++x+x]
let mut vec = vec![
2, 4, 6, 7, 9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 27, 29, 31, 33, 34, 35, 36,
];
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
assert_eq!(removed.len(), 10);
assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]);
assert_eq!(vec.len(), 11);
assert_eq!(vec, vec![7, 9, 11, 13, 15, 17, 27, 29, 31, 33, 35]);
}
{
// [xxxxxxxxxx+++++++++++]
let mut vec = vec![
2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19,
];
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
assert_eq!(removed.len(), 10);
assert_eq!(removed, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20]);
assert_eq!(vec.len(), 10);
assert_eq!(vec, vec![1, 3, 5, 7, 9, 11, 13, 15, 17, 19]);
}
{
// [+++++++++++xxxxxxxxxx]
let mut vec = vec![
1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
];
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
assert_eq!(removed.len(), 10);
assert_eq!(removed, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20]);
assert_eq!(vec.len(), 10);
assert_eq!(vec, vec![1, 3, 5, 7, 9, 11, 13, 15, 17, 19]);
}
}
}

View file

@ -3,17 +3,13 @@
//! This crate contains facilities and types for parsing `.slangp` shader presets files.
//!
//! Shader presets contain shader and texture parameters, and the order in which to apply a set of
//! shaders in a filter chain. A librashader runtime takes a resulting [`ShaderPreset`](crate::ShaderPreset)
//! shaders in a filter chain. A librashader runtime takes a resulting [`ShaderPreset`]
//! as input to create a filter chain.
//!
//! Re-exported as [`librashader::presets`](https://docs.rs/librashader/latest/librashader/presets/index.html).
#![allow(stable_features)]
#![allow(unstable_name_collisions)]
pub mod context;
mod error;
mod extract_if;
mod parse;
mod preset;

View file

@ -1,404 +0,0 @@
//! Shader preset wildcard replacement context handling.
//!
//! Implements wildcard replacement of shader paths specified in
//! [RetroArch#15023](https://github.com/libretro/RetroArch/pull/15023).
use once_cell::sync::Lazy;
use regex::bytes::Regex;
use rustc_hash::FxHashMap;
use std::collections::VecDeque;
use std::ffi::{OsStr, OsString};
use std::fmt::{Debug, Display, Formatter};
use std::ops::Add;
use std::path::{Component, Path, PathBuf};
/// Valid video driver or runtime. This list is non-exhaustive.
#[repr(u32)]
#[non_exhaustive]
#[derive(Debug, Copy, Clone)]
pub enum VideoDriver {
/// None (`null`)
None = 0,
/// OpenGL Core (`glcore`)
GlCore,
/// Legacy OpenGL (`gl`)
Gl,
/// Vulkan (`vulkan`)
Vulkan,
/// Direct3D 9 (`d3d9_hlsl`)
Direct3D9Hlsl,
/// Direct3D 11 (`d3d11`)
Direct3D11,
/// Direct3D12 (`d3d12`)
Direct3D12,
/// Metal (`metal`)
Metal,
}
impl Display for VideoDriver {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
VideoDriver::None => f.write_str("null"),
VideoDriver::GlCore => f.write_str("glcore"),
VideoDriver::Gl => f.write_str("gl"),
VideoDriver::Vulkan => f.write_str("vulkan"),
VideoDriver::Direct3D11 => f.write_str("d3d11"),
VideoDriver::Direct3D9Hlsl => f.write_str("d3d9_hlsl"),
VideoDriver::Direct3D12 => f.write_str("d3d12"),
VideoDriver::Metal => f.write_str("metal"),
}
}
}
/// Valid extensions for shader extensions.
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum ShaderExtension {
/// `.slang`
Slang = 0,
/// `.glsl`
Glsl,
/// `.cg`
Cg,
}
impl Display for ShaderExtension {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ShaderExtension::Slang => f.write_str("slang"),
ShaderExtension::Glsl => f.write_str("glsl"),
ShaderExtension::Cg => f.write_str("cg"),
}
}
}
/// Valid extensions for shader presets
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum PresetExtension {
/// `.slangp`
Slangp = 0,
/// `.glslp`
Glslp,
/// `.cgp`
Cgp,
}
impl Display for PresetExtension {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PresetExtension::Slangp => f.write_str("slangp"),
PresetExtension::Glslp => f.write_str("glslp"),
PresetExtension::Cgp => f.write_str("cgp"),
}
}
}
/// Rotation of the viewport.
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum Rotation {
/// Zero
Zero = 0,
/// 90 degrees
Right = 1,
/// 180 degrees
Straight = 2,
/// 270 degrees
Reflex = 3,
}
impl From<u32> for Rotation {
fn from(value: u32) -> Self {
let value = value % 4;
match value {
0 => Rotation::Zero,
1 => Rotation::Right,
2 => Rotation::Straight,
3 => Rotation::Reflex,
_ => unreachable!(),
}
}
}
impl Add for Rotation {
type Output = Rotation;
fn add(self, rhs: Self) -> Self::Output {
let lhs = self as u32;
let out = lhs + rhs as u32;
Rotation::from(out)
}
}
impl Display for Rotation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Rotation::Zero => f.write_str("0"),
Rotation::Right => f.write_str("90"),
Rotation::Straight => f.write_str("180"),
Rotation::Reflex => f.write_str("270"),
}
}
}
/// Orientation of aspect ratios
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum Orientation {
/// Vertical orientation.
Vertical = 0,
/// Horizontal orientation.
Horizontal,
}
impl Display for Orientation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Orientation::Vertical => f.write_str("VERT"),
Orientation::Horizontal => f.write_str("HORZ"),
}
}
}
/// An item representing a variable that can be replaced in a path preset.
#[derive(Debug, Clone)]
pub enum ContextItem {
/// The content directory of the game (`CONTENT-DIR`)
ContentDirectory(String),
/// The name of the libretro core (`CORE`)
CoreName(String),
/// The filename of the game (`GAME`)
GameName(String),
/// The name of the preset (`PRESET`)
Preset(String),
/// The name of the preset directory (`PRESET_DIR`)
PresetDirectory(String),
/// The video driver (runtime) (`VID-DRV`)
VideoDriver(VideoDriver),
/// The extension of shader types supported by the driver (`VID-DRV-SHADER-EXT`)
VideoDriverShaderExtension(ShaderExtension),
/// The extension of shader presets supported by the driver (`VID-DRV-PRESET-EXT`)
VideoDriverPresetExtension(PresetExtension),
/// The rotation that the core is requesting (`CORE-REQ-ROT`)
CoreRequestedRotation(Rotation),
/// Whether or not to allow core-requested rotation (`VID-ALLOW-CORE-ROT`)
AllowCoreRotation(bool),
/// The rotation the user is requesting (`VID-USER-ROT`)
UserRotation(Rotation),
/// The final rotation (`VID-FINAL-ROT`) calculated by the sum of `VID-USER-ROT` and `CORE-REQ-ROT`
FinalRotation(Rotation),
/// The user-adjusted screen orientation (`SCREEN-ORIENT`)
ScreenOrientation(Rotation),
/// The orientation of the viewport aspect ratio (`VIEW-ASPECT-ORIENT`)
ViewAspectOrientation(Orientation),
/// The orientation of the aspect ratio requested by the core (`CORE-ASPECT-ORIENT`)
CoreAspectOrientation(Orientation),
/// An external, arbitrary context variable.
ExternContext(String, String),
}
impl ContextItem {
fn toggle_str(v: bool) -> &'static str {
if v {
"ON"
} else {
"OFF"
}
}
pub fn key(&self) -> &str {
match self {
ContextItem::ContentDirectory(_) => "CONTENT-DIR",
ContextItem::CoreName(_) => "CORE",
ContextItem::GameName(_) => "GAME",
ContextItem::Preset(_) => "PRESET",
ContextItem::PresetDirectory(_) => "PRESET_DIR",
ContextItem::VideoDriver(_) => "VID-DRV",
ContextItem::CoreRequestedRotation(_) => "CORE-REQ-ROT",
ContextItem::AllowCoreRotation(_) => "VID-ALLOW-CORE-ROT",
ContextItem::UserRotation(_) => "VID-USER-ROT",
ContextItem::FinalRotation(_) => "VID-FINAL-ROT",
ContextItem::ScreenOrientation(_) => "SCREEN-ORIENT",
ContextItem::ViewAspectOrientation(_) => "VIEW-ASPECT-ORIENT",
ContextItem::CoreAspectOrientation(_) => "CORE-ASPECT-ORIENT",
ContextItem::VideoDriverShaderExtension(_) => "VID-DRV-SHADER-EXT",
ContextItem::VideoDriverPresetExtension(_) => "VID-DRV-PRESET-EXT",
ContextItem::ExternContext(key, _) => key,
}
}
}
impl Display for ContextItem {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ContextItem::ContentDirectory(v) => f.write_str(v),
ContextItem::CoreName(v) => f.write_str(v),
ContextItem::GameName(v) => f.write_str(v),
ContextItem::Preset(v) => f.write_str(v),
ContextItem::PresetDirectory(v) => f.write_str(v),
ContextItem::VideoDriver(v) => f.write_fmt(format_args!("{}", v)),
ContextItem::CoreRequestedRotation(v) => {
f.write_fmt(format_args!("{}-{}", self.key(), v))
}
ContextItem::AllowCoreRotation(v) => f.write_fmt(format_args!(
"{}-{}",
self.key(),
ContextItem::toggle_str(*v)
)),
ContextItem::UserRotation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
ContextItem::FinalRotation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
ContextItem::ScreenOrientation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
ContextItem::ViewAspectOrientation(v) => {
f.write_fmt(format_args!("{}-{}", self.key(), v))
}
ContextItem::CoreAspectOrientation(v) => {
f.write_fmt(format_args!("{}-{}", self.key(), v))
}
ContextItem::VideoDriverShaderExtension(v) => f.write_fmt(format_args!("{}", v)),
ContextItem::VideoDriverPresetExtension(v) => f.write_fmt(format_args!("{}", v)),
ContextItem::ExternContext(_, v) => f.write_fmt(format_args!("{}", v)),
}
}
}
/// A preset wildcard context.
///
/// Any items added after will have higher priority
/// when passed to the shader preset parser.
///
/// When passed to the preset parser, the preset parser
/// will automatically add inferred items at lowest priority.
///
/// Any items added by the user will override the automatically
/// inferred items.
#[derive(Debug, Clone)]
pub struct WildcardContext(VecDeque<ContextItem>);
impl WildcardContext {
/// Create a new wildcard context.
pub fn new() -> Self {
Self(VecDeque::new())
}
/// Prepend an item to the context builder.
pub fn prepend_item(&mut self, item: ContextItem) {
self.0.push_front(item);
}
/// Append an item to the context builder.
/// The new item will take precedence over all items added before it.
pub fn append_item(&mut self, item: ContextItem) {
self.0.push_back(item);
}
/// Prepend sensible defaults for the given video driver.
///
/// Any values added, either previously or afterwards will not be overridden.
pub fn add_video_driver_defaults(&mut self, video_driver: VideoDriver) {
self.prepend_item(ContextItem::VideoDriverPresetExtension(
PresetExtension::Slangp,
));
self.prepend_item(ContextItem::VideoDriverShaderExtension(
ShaderExtension::Slang,
));
self.prepend_item(ContextItem::VideoDriver(video_driver));
}
/// Prepend default entries from the path of the preset.
///
/// Any values added, either previously or afterwards will not be overridden.
pub fn add_path_defaults(&mut self, path: impl AsRef<Path>) {
let path = path.as_ref();
if let Some(preset_name) = path.file_stem() {
let preset_name = preset_name.to_string_lossy();
self.prepend_item(ContextItem::Preset(preset_name.into()))
}
if let Some(preset_dir_name) = path.parent().and_then(|p| {
if !p.is_dir() {
return None;
};
p.file_name()
}) {
let preset_dir_name = preset_dir_name.to_string_lossy();
self.prepend_item(ContextItem::PresetDirectory(preset_dir_name.into()))
}
}
pub(crate) fn to_hashmap(mut self) -> FxHashMap<String, String> {
let mut map = FxHashMap::default();
let last_user_rot = self
.0
.iter()
.rfind(|i| matches!(i, ContextItem::UserRotation(_)));
let last_core_rot = self
.0
.iter()
.rfind(|i| matches!(i, ContextItem::CoreRequestedRotation(_)));
let final_rot = match (last_core_rot, last_user_rot) {
(Some(ContextItem::UserRotation(u)), None) => Some(ContextItem::FinalRotation(*u)),
(None, Some(ContextItem::CoreRequestedRotation(c))) => {
Some(ContextItem::FinalRotation(*c))
}
(Some(ContextItem::UserRotation(u)), Some(ContextItem::CoreRequestedRotation(c))) => {
Some(ContextItem::FinalRotation(*u + *c))
}
_ => None,
};
if let Some(final_rot) = final_rot {
self.prepend_item(final_rot);
}
for item in self.0 {
map.insert(String::from(item.key()), item.to_string());
}
map
}
}
pub(crate) fn apply_context(path: &mut PathBuf, context: &FxHashMap<String, String>) {
static WILDCARD_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\$([A-Z-_]+)\\$").unwrap());
if context.is_empty() {
return;
}
// Don't want to do any extra work if there's no match.
if !WILDCARD_REGEX.is_match(path.as_os_str().as_encoded_bytes()) {
return;
}
let mut new_path = PathBuf::with_capacity(path.capacity());
for component in path.components() {
match component {
Component::Normal(path) => {
let haystack = path.as_encoded_bytes();
let replaced =
WILDCARD_REGEX.replace_all(haystack, |caps: &regex::bytes::Captures| {
let Some(name) = caps.get(1) else {
return caps[0].to_vec();
};
let Ok(key) = std::str::from_utf8(name.as_bytes()) else {
return caps[0].to_vec();
};
if let Some(replacement) = context.get(key) {
return OsString::from(replacement.to_string()).into_encoded_bytes();
}
return caps[0].to_vec();
});
// SAFETY: The original source is valid encoded bytes, and our replacement is
// valid encoded bytes. This upholds the safety requirements of `from_encoded_bytes_unchecked`.
new_path.push(unsafe { OsStr::from_encoded_bytes_unchecked(&replaced.as_ref()) })
}
_ => new_path.push(component),
}
}
// If no wildcards are found within the path, or the path after replacing the wildcards does not exist on disk, the path returned will be unaffected.
if let Ok(true) = new_path.try_exists() {
*path = new_path;
}
}

View file

@ -1,42 +1,46 @@
use crate::extract_if::MakeExtractIf;
use crate::parse::remove_if;
use crate::parse::value::Value;
use crate::{ParameterConfig, Scale2D, Scaling, ShaderPassConfig, ShaderPreset, TextureConfig};
use crate::{
ParameterMeta, PassConfig, PassMeta, Scale2D, Scaling, ShaderPreset, TextureConfig, TextureMeta,
};
use vec_extract_if_polyfill::MakeExtractIf;
pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
let textures: Vec<TextureConfig> = values
.extract_if(|f| matches!(*f, Value::Texture { .. }))
.map(|value| {
if let Value::Texture {
name,
filter_mode,
wrap_mode,
mipmap,
path,
} = value
{
TextureConfig {
let textures: Vec<TextureConfig> =
MakeExtractIf::extract_if(&mut values, |f| matches!(*f, Value::Texture { .. }))
.map(|value| {
if let Value::Texture {
name,
path,
wrap_mode,
filter_mode,
wrap_mode,
mipmap,
path,
} = value
{
TextureConfig {
storage: librashader_common::StorageType::Path(path),
meta: TextureMeta {
name,
wrap_mode,
filter_mode,
mipmap,
},
}
} else {
unreachable!("values should all be of type Texture")
}
} else {
unreachable!("values should all be of type Texture")
}
})
.collect();
let parameters: Vec<ParameterConfig> = values
.extract_if(|f| matches!(*f, Value::Parameter { .. }))
.map(|value| {
if let Value::Parameter(name, value) = value {
ParameterConfig { name, value }
} else {
unreachable!("values should be all of type parameters")
}
})
.collect();
})
.collect();
let parameters: Vec<ParameterMeta> =
MakeExtractIf::extract_if(&mut values, |f| matches!(*f, Value::Parameter { .. }))
.map(|value| {
if let Value::Parameter(name, value) = value {
ParameterMeta { name, value }
} else {
unreachable!("values should be all of type parameters")
}
})
.collect();
let mut shaders = Vec::new();
let shader_count =
@ -64,9 +68,9 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
&mut values,
|v| matches!(*v, Value::Shader(shader_index, _) if shader_index == shader),
) {
let shader_values: Vec<Value> = values
.extract_if(|v| v.shader_index() == Some(shader))
.collect();
let shader_values: Vec<Value> =
MakeExtractIf::extract_if(&mut values, |v| v.shader_index() == Some(shader))
.collect();
let scale_type = shader_values.iter().find_map(|f| match f {
Value::ScaleType(_, value) => Some(*value),
_ => None,
@ -113,64 +117,66 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
scale_y = scale;
}
let shader = ShaderPassConfig {
id,
name: librashader_common::ShaderStorage::Path(name),
alias: shader_values.iter().find_map(|f| match f {
Value::Alias(_, value) => Some(value.to_string()),
_ => None,
}),
filter: shader_values
.iter()
.find_map(|f| match f {
Value::FilterMode(_, value) => Some(*value),
let shader = PassConfig {
storage: librashader_common::StorageType::Path(name),
meta: PassMeta {
id,
alias: shader_values.iter().find_map(|f| match f {
Value::Alias(_, value) => Some(value.clone()),
_ => None,
})
.unwrap_or_default(),
wrap_mode: shader_values
.iter()
.find_map(|f| match f {
Value::WrapMode(_, value) => Some(*value),
_ => None,
})
.unwrap_or_default(),
frame_count_mod: shader_values
.iter()
.find_map(|f| match f {
Value::FrameCountMod(_, value) => Some(*value),
_ => None,
})
.unwrap_or(0),
srgb_framebuffer: shader_values
.iter()
.find_map(|f| match f {
Value::SrgbFramebuffer(_, value) => Some(*value),
_ => None,
})
.unwrap_or(false),
float_framebuffer: shader_values
.iter()
.find_map(|f| match f {
Value::FloatFramebuffer(_, value) => Some(*value),
_ => None,
})
.unwrap_or(false),
mipmap_input: shader_values
.iter()
.find_map(|f| match f {
Value::MipmapInput(_, value) => Some(*value),
_ => None,
})
.unwrap_or(false),
scaling: Scale2D {
valid: scale_valid,
x: Scaling {
scale_type: scale_type_x.unwrap_or_default(),
factor: scale_x.unwrap_or_default(),
},
y: Scaling {
scale_type: scale_type_y.unwrap_or_default(),
factor: scale_y.unwrap_or_default(),
}),
filter: shader_values
.iter()
.find_map(|f| match f {
Value::FilterMode(_, value) => Some(*value),
_ => None,
})
.unwrap_or_default(),
wrap_mode: shader_values
.iter()
.find_map(|f| match f {
Value::WrapMode(_, value) => Some(*value),
_ => None,
})
.unwrap_or_default(),
frame_count_mod: shader_values
.iter()
.find_map(|f| match f {
Value::FrameCountMod(_, value) => Some(*value),
_ => None,
})
.unwrap_or(0),
srgb_framebuffer: shader_values
.iter()
.find_map(|f| match f {
Value::SrgbFramebuffer(_, value) => Some(*value),
_ => None,
})
.unwrap_or(false),
float_framebuffer: shader_values
.iter()
.find_map(|f| match f {
Value::FloatFramebuffer(_, value) => Some(*value),
_ => None,
})
.unwrap_or(false),
mipmap_input: shader_values
.iter()
.find_map(|f| match f {
Value::MipmapInput(_, value) => Some(*value),
_ => None,
})
.unwrap_or(false),
scaling: Scale2D {
valid: scale_valid,
x: Scaling {
scale_type: scale_type_x.unwrap_or_default(),
factor: scale_x.unwrap_or_default(),
},
y: Scaling {
scale_type: scale_type_y.unwrap_or_default(),
factor: scale_y.unwrap_or_default(),
},
},
},
};
@ -182,8 +188,8 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
ShaderPreset {
#[cfg(feature = "parse_legacy_glsl")]
feedback_pass,
shader_count,
shaders,
pass_count: shader_count,
passes: shaders,
textures,
parameters,
}

View file

@ -10,7 +10,7 @@ use nom::IResult;
use num_traits::cast::ToPrimitive;
use crate::parse::token::do_lex;
use librashader_common::map::FastHashMap;
use librashader_common::map::{FastHashMap, ShortString};
use librashader_common::{FilterMode, WrapMode};
use std::fs::File;
use std::io::Read;
@ -18,7 +18,7 @@ use std::path::{Path, PathBuf};
use std::str::FromStr;
use crate::context::{apply_context, WildcardContext};
use crate::extract_if::MakeExtractIf;
use vec_extract_if_polyfill::MakeExtractIf;
#[derive(Debug)]
pub enum Value {
@ -37,10 +37,10 @@ pub enum Value {
FloatFramebuffer(i32, bool),
SrgbFramebuffer(i32, bool),
MipmapInput(i32, bool),
Alias(i32, String),
Parameter(String, f32),
Alias(i32, ShortString),
Parameter(ShortString, f32),
Texture {
name: String,
name: ShortString,
filter_mode: FilterMode,
wrap_mode: WrapMode,
mipmap: bool,
@ -195,8 +195,10 @@ fn load_child_reference_strings(
.map_err(|e| ParsePresetError::IOError(path.clone(), e))?;
let mut new_tokens = do_lex(&reference_contents)?;
let new_references: Vec<PathBuf> = new_tokens
.extract_if(|token| *token.key.fragment() == "#reference")
let new_references: Vec<PathBuf> =
MakeExtractIf::extract_if(&mut new_tokens, |token| {
*token.key.fragment() == "#reference"
})
.map(|value| PathBuf::from(*value.value.fragment()))
.collect();
@ -217,7 +219,7 @@ pub(crate) fn parse_preset(
) -> Result<Vec<Value>, ParsePresetError> {
let path = path.as_ref();
let mut path = path.to_path_buf();
let context = context.to_hashmap();
let context = context.into_hashmap();
apply_context(&mut path, &context);
@ -250,10 +252,10 @@ pub fn parse_values(
root_path.pop();
}
let references: Vec<PathBuf> = tokens
.extract_if(|token| *token.key.fragment() == "#reference")
.map(|value| PathBuf::from(*value.value.fragment()))
.collect();
let references: Vec<PathBuf> =
MakeExtractIf::extract_if(&mut tokens, |token| *token.key.fragment() == "#reference")
.map(|value| PathBuf::from(*value.value.fragment()))
.collect();
// unfortunately we need to lex twice because there's no way to know the references ahead of time.
// the returned references should have context applied
@ -275,10 +277,12 @@ pub fn parse_values(
// collect all possible parameter names.
let mut parameter_names: Vec<&str> = Vec::new();
for (_, tokens) in all_tokens.iter_mut() {
for token in tokens.extract_if(|token| *token.key.fragment() == "parameters") {
for token in
MakeExtractIf::extract_if(tokens, |token| *token.key.fragment() == "parameters")
{
let parameter_name_string: &str = token.value.fragment();
for parameter_name in parameter_name_string.split(';') {
parameter_names.push(parameter_name);
parameter_names.push(parameter_name.trim());
}
}
}
@ -286,10 +290,11 @@ pub fn parse_values(
// collect all possible texture names.
let mut texture_names: Vec<&str> = Vec::new();
for (_, tokens) in all_tokens.iter_mut() {
for token in tokens.extract_if(|token| *token.key.fragment() == "textures") {
for token in MakeExtractIf::extract_if(tokens, |token| *token.key.fragment() == "textures")
{
let texture_name_string: &str = token.value.fragment();
for texture_name in texture_name_string.split(';') {
texture_names.push(texture_name);
texture_names.push(texture_name.trim());
}
}
}
@ -297,7 +302,9 @@ pub fn parse_values(
let mut values = Vec::new();
// resolve shader paths.
for (path, tokens) in all_tokens.iter_mut() {
for token in tokens.extract_if(|token| parse_indexed_key("shader", token.key).is_ok()) {
for token in MakeExtractIf::extract_if(tokens, |token| {
parse_indexed_key("shader", token.key).is_ok()
}) {
let (_, index) = parse_indexed_key("shader", token.key).map_err(|e| match e {
nom::Err::Error(e) | nom::Err::Failure(e) => {
let input: Span = e.input;
@ -328,8 +335,11 @@ pub fn parse_values(
// resolve texture paths
let mut textures = Vec::new();
for (path, tokens) in all_tokens.iter_mut() {
for token in tokens.extract_if(|token| texture_names.contains(token.key.fragment())) {
for token in
MakeExtractIf::extract_if(tokens, |token| texture_names.contains(token.key.fragment()))
{
let mut relative_path = path.to_path_buf();
// Don't trim paths
relative_path.push(*token.value.fragment());
relative_path
.canonicalize()
@ -380,7 +390,7 @@ pub fn parse_values(
.map_or(None, |(_, v)| Some(FilterMode::from_str(&v.value).unwrap()));
values.push(Value::Texture {
name: texture.to_string(),
name: ShortString::from(*texture.fragment()),
filter_mode: filter.unwrap_or(if linear {
FilterMode::Linear
} else {
@ -395,7 +405,7 @@ pub fn parse_values(
let mut rest_tokens = Vec::new();
// hopefully no more textures left in the token tree
for (p, token) in tokens {
if parameter_names.contains(token.key.fragment()) {
if parameter_names.contains(&token.key.fragment().trim()) {
let param_val = from_float(token.value)
// This is literally just to work around BEAM_PROFILE in crt-hyllian-sinc-glow.slangp
// which has ""0'.000000". This somehow works in RA because it defaults to 0, probably.
@ -403,7 +413,7 @@ pub fn parse_values(
// params (god help me), it would be pretty bad because we lose texture path fallback.
.unwrap_or(0.0);
values.push(Value::Parameter(
token.key.fragment().to_string(),
ShortString::from(token.key.fragment().trim()),
param_val,
));
continue;
@ -484,7 +494,10 @@ pub fn parse_values(
}
if let Ok((_, idx)) = parse_indexed_key("alias", token.key) {
values.push(Value::Alias(idx, token.value.to_string()));
values.push(Value::Alias(
idx,
ShortString::from(token.value.fragment().trim()),
));
continue;
}
if let Ok((_, idx)) = parse_indexed_key("scale_type", token.key) {
@ -547,7 +560,7 @@ pub fn parse_values(
// handle undeclared parameters after parsing everything else as a last resort.
if let Ok(param_val) = from_float(token.value) {
values.push(Value::Parameter(
token.key.fragment().to_string(),
ShortString::from(token.key.fragment().trim()),
param_val,
));
}
@ -558,6 +571,7 @@ pub fn parse_values(
.all(|k| !token.key.ends_with(k))
{
let mut relative_path = path.to_path_buf();
// Don't trim paths.
relative_path.push(*token.value.fragment());
relative_path
.canonicalize()
@ -596,7 +610,7 @@ pub fn parse_values(
});
values.push(Value::Texture {
name: texture.to_string(),
name: ShortString::from(*texture.fragment()),
filter_mode: if linear {
FilterMode::Linear
} else {
@ -621,7 +635,7 @@ mod test {
#[test]
pub fn parse_basic() {
let root =
PathBuf::from("../test/slang-shaders/bezel/Mega_Bezel/Presets/Base_CRT_Presets/MBZ__3__STD__MEGATRON-NTSC.slangp");
PathBuf::from("../test/shaders_slang/bezel/Mega_Bezel/Presets/Base_CRT_Presets/MBZ__3__STD__MEGATRON-NTSC.slangp");
let basic = parse_preset(root, WildcardContext::new());
eprintln!("{basic:?}");
assert!(basic.is_ok());

View file

@ -1,18 +1,32 @@
use crate::error::ParsePresetError;
use librashader_common::map::ShortString;
use librashader_common::{FilterMode, ImageFormat, WrapMode};
use std::ops::Mul;
use std::path::PathBuf;
use std::str::FromStr;
/// The configuration for a single shader pass.
pub type PassConfig = PathReference<PassMeta>;
/// Configuration options for a lookup texture used in the shader.
pub type TextureConfig = PathReference<TextureMeta>;
/// A reference to a resource on disk.
#[derive(Debug, Clone)]
pub struct ShaderPassConfig {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PathReference<M> {
pub storage: librashader_common::StorageType,
/// Meta information about the resource.
pub meta: M,
}
/// Meta information about a shader pass.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PassMeta {
/// The index of the shader pass relative to its parent preset.
pub id: i32,
/// The fully qualified path to the shader pass source file.
pub name: librashader_common::ShaderStorage,
/// The alias of the shader pass if available.
pub alias: Option<String>,
pub alias: Option<ShortString>,
/// The filtering mode that this shader pass should expect.
pub filter: FilterMode,
/// The texture addressing (wrap) mode that this shader pass expects.
@ -29,7 +43,7 @@ pub struct ShaderPassConfig {
pub scaling: Scale2D,
}
impl ShaderPassConfig {
impl PassMeta {
/// If the framebuffer expects a different format than what was defined in the
/// shader source, returns such format.
#[inline(always)]
@ -54,6 +68,7 @@ impl ShaderPassConfig {
#[repr(i32)]
#[derive(Default, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// The scaling type for the shader pass.
pub enum ScaleType {
#[default]
@ -69,6 +84,7 @@ pub enum ScaleType {
/// The scaling factor for framebuffer scaling.
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ScaleFactor {
/// Scale by a fractional float factor.
Float(f32),
@ -129,6 +145,7 @@ impl FromStr for ScaleType {
/// Framebuffer scaling parameters.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Scaling {
/// The method to scale the framebuffer with.
pub scale_type: ScaleType,
@ -138,6 +155,7 @@ pub struct Scaling {
/// 2D quad scaling parameters.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Scale2D {
/// Whether or not this combination of scaling factors is valid.
pub valid: bool,
@ -149,24 +167,24 @@ pub struct Scale2D {
/// Configuration options for a lookup texture used in the shader.
#[derive(Debug, Clone)]
pub struct TextureConfig {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TextureMeta {
/// The name of the texture.
pub name: String,
/// The fully qualified path to the texture.
pub path: PathBuf,
pub name: ShortString,
/// The wrap (addressing) mode to use when sampling the texture.
pub wrap_mode: WrapMode,
/// The filter mode to use when sampling the texture.
pub filter_mode: FilterMode,
/// Whether or not to generate mipmaps for this texture.
/// Whether to generate mipmaps for this texture.
pub mipmap: bool,
}
/// Configuration options for a shader parameter.
#[derive(Debug, Clone)]
pub struct ParameterConfig {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ParameterMeta {
/// The name of the parameter.
pub name: String,
pub name: ShortString,
/// The value it is set to in the preset.
pub value: f32,
}
@ -176,6 +194,7 @@ pub struct ParameterConfig {
/// A shader preset can be used to create a filter chain runtime instance, or reflected to get
/// parameter metadata.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShaderPreset {
/// Used in legacy GLSL shader semantics. If < 0, no feedback pass is used.
/// Otherwise, the FBO after pass #N is passed a texture to next frame
@ -183,14 +202,14 @@ pub struct ShaderPreset {
pub feedback_pass: i32,
/// The number of shaders enabled in the filter chain.
pub shader_count: i32,
pub pass_count: i32,
// Everything is in Vecs because the expect number of values is well below 64.
/// Preset information for each shader.
pub shaders: Vec<ShaderPassConfig>,
pub passes: Vec<PassConfig>,
/// Preset information for each texture.
pub textures: Vec<TextureConfig>,
/// Preset information for each user parameter.
pub parameters: Vec<ParameterConfig>,
pub parameters: Vec<ParameterMeta>,
}

View file

@ -3,7 +3,7 @@ name = "librashader-reflect"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.3.0"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,26 +12,24 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[dependencies]
glslang = "0.3"
glslang = "0.6.0"
bytemuck = "1.13.0"
thiserror = "1.0.37"
bitflags = "2.4.2"
librashader-common = { path = "../librashader-common", version = "0.3.0" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.3.0" }
librashader-presets = { path = "../librashader-presets", version = "0.3.0" }
librashader-common = { path = "../librashader-common", version = "0.5.1" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.5.1" }
librashader-presets = { path = "../librashader-presets", version = "0.5.1" }
librashader-pack = { path = "../librashader-pack", version = "0.5.1" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1", optional = true }
spirv-cross2 = { workspace = true, optional = true }
naga = { version = "22", optional = true }
rspirv = { version = "0.12.0", optional = true }
spirv = { version = "0.3.0", optional = true}
serde = { version = "1.0", features = ["derive"], optional = true }
indexmap = { version = "2.1.0", features = [] }
matches = { version = "0.1.10", features = [] }
rustc-hash = "2.0.0"
[target.'cfg(windows)'.dependencies.spirv-to-dxil]
@ -39,12 +37,14 @@ version = "0.4.7"
optional = true
[features]
default = ["cross", "naga", "serialize", "wgsl", "msl"]
dxil = ["spirv_cross/hlsl", "spirv-to-dxil"]
wgsl = ["cross", "naga/wgsl-out", "spirv", "rspirv"]
cross = [ "spirv_cross", "spirv_cross/glsl", "spirv_cross/hlsl", "spirv_cross/msl" ]
naga = [ "rspirv", "spirv", "naga/spv-in", "naga/spv-out", "naga/wgsl-out", "naga/msl-out" ]
serialize = [ "serde" ]
msl = [ "spirv_cross/msl", "naga/msl-out" ]
default = ["cross", "naga", "wgsl", "msl"]
dxil = [ "spirv-cross2?/hlsl", "dep:spirv-to-dxil" ]
wgsl = [ "cross", "naga", "naga/wgsl-out", "dep:spirv", "dep:rspirv"]
cross = [ "spirv-cross2", "spirv-cross2/glsl", "spirv-cross2/hlsl", "spirv-cross2/msl" ]
naga = [ "dep:rspirv", "dep:spirv", "dep:naga", "naga/spv-in", "naga/spv-out", "naga/wgsl-out", "naga/msl-out" ]
serde = ["dep:serde", "serde/derive", "librashader-common/serde", "bitflags/serde"]
msl = [ "cross", "spirv-cross2/msl", "naga?/msl-out" ]
unstable-naga-in = ["naga/glsl-in"]
stable = []
unstable-naga-in = ["naga/glsl-in"]

View file

@ -1,11 +1,12 @@
use crate::back::spirv::WriteSpirV;
use crate::back::targets::{OutputTarget, DXIL};
use crate::back::{CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput};
use crate::back::{
CompileReflectShader, CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput,
};
use crate::error::{ShaderCompileError, ShaderReflectError};
use crate::front::SpirvCompilation;
use crate::reflect::cross::glsl::GlslReflect;
use crate::reflect::cross::SpirvCross;
use crate::reflect::ReflectShader;
pub use spirv_to_dxil::DxilObject;
pub use spirv_to_dxil::ShaderModel;
use spirv_to_dxil::{
@ -16,12 +17,12 @@ impl OutputTarget for DXIL {
type Output = DxilObject;
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, SpirvCross> for DXIL {
type Target = DXIL;
type Options = Option<ShaderModel>;
type Context = ();
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
fn from_compilation(
compile: SpirvCompilation,
@ -38,6 +39,28 @@ impl FromCompilation<SpirvCompilation, SpirvCross> for DXIL {
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, SpirvCross> for DXIL {
type Target = DXIL;
type Options = Option<ShaderModel>;
type Context = ();
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
let reflect = GlslReflect::try_from(&compile)?;
Ok(CompilerBackend {
// we can just reuse WriteSpirV as the backend.
backend: Box::new(WriteSpirV {
reflect,
vertex: compile.vertex,
fragment: compile.fragment,
}),
})
}
}
impl CompileShader<DXIL> for WriteSpirV {
type Options = Option<ShaderModel>;
type Context = ();
@ -88,4 +111,11 @@ impl CompileShader<DXIL> for WriteSpirV {
context: (),
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<DxilObject, Self::Context>, ShaderCompileError> {
<WriteSpirV as CompileShader<DXIL>>::compile(*self, options)
}
}

View file

@ -1,12 +1,11 @@
use crate::back::targets::GLSL;
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::back::{CompileReflectShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::cross::{CompiledProgram, SpirvCross};
use crate::reflect::ReflectShader;
/// The GLSL version to target.
pub use spirv_cross::glsl::Version as GlslVersion;
pub use spirv_cross2::compile::glsl::GlslVersion;
use crate::reflect::cross::glsl::GlslReflect;
@ -15,15 +14,15 @@ pub struct CrossGlslContext {
/// A map of bindings of sampler names to binding locations.
pub sampler_bindings: Vec<(String, u32)>,
/// The compiled program artifact after compilation.
pub artifact: CompiledProgram<spirv_cross::glsl::Target>,
pub artifact: CompiledProgram<spirv_cross2::targets::Glsl>,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, SpirvCross> for GLSL {
type Target = GLSL;
type Options = GlslVersion;
type Context = CrossGlslContext;
type Output = impl CompileShader<Self::Target, Options = GlslVersion, Context = Self::Context>
+ ReflectShader;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
fn from_compilation(
compile: SpirvCompilation,
@ -33,3 +32,19 @@ impl FromCompilation<SpirvCompilation, SpirvCross> for GLSL {
})
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, SpirvCross> for GLSL {
type Target = GLSL;
type Options = GlslVersion;
type Context = CrossGlslContext;
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: Box::new(GlslReflect::try_from(&compile)?),
})
}
}

View file

@ -1,13 +1,12 @@
use crate::back::targets::HLSL;
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::back::{CompileReflectShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::cross::hlsl::HlslReflect;
use crate::reflect::cross::{CompiledProgram, SpirvCross};
use crate::reflect::ReflectShader;
/// The HLSL shader model version to target.
pub use spirv_cross::hlsl::ShaderModel as HlslShaderModel;
pub use spirv_cross2::compile::hlsl::HlslShaderModel;
/// Buffer assignment information
#[derive(Debug, Clone)]
@ -47,7 +46,7 @@ impl HlslBufferAssignments {
{
return true;
}
return false;
false
}
// Check if the mangled name matches.
@ -92,24 +91,24 @@ impl HlslBufferAssignments {
return true;
}
return false;
false
}
}
/// The context for a HLSL compilation via spirv-cross.
pub struct CrossHlslContext {
/// The compiled HLSL program.
pub artifact: CompiledProgram<spirv_cross::hlsl::Target>,
pub artifact: CompiledProgram<spirv_cross2::targets::Hlsl>,
pub vertex_buffers: HlslBufferAssignments,
pub fragment_buffers: HlslBufferAssignments,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, SpirvCross> for HLSL {
type Target = HLSL;
type Options = Option<HlslShaderModel>;
type Context = CrossHlslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
fn from_compilation(
compile: SpirvCompilation,
@ -120,6 +119,22 @@ impl FromCompilation<SpirvCompilation, SpirvCross> for HLSL {
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, SpirvCross> for HLSL {
type Target = HLSL;
type Options = Option<HlslShaderModel>;
type Context = CrossHlslContext;
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: Box::new(HlslReflect::try_from(&compile)?),
})
}
}
#[cfg(test)]
mod test {
use crate::back::hlsl::HlslBufferAssignments;

View file

@ -32,17 +32,30 @@ pub trait CompileShader<T: OutputTarget> {
type Context;
/// Consume the object and return the compiled output of the shader.
///
/// The shader should either be reflected or validated as
/// [ReflectShader] before being compiled, or the results may not be valid.
fn compile(
self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<T::Output, Self::Context>, ShaderCompileError>;
/// Consume the object and return the compiled output of the shader.
///
/// This is an internal implementation detail for stable building without TAIT,
/// to allow delegation when Self is unsized (i.e. dyn CompileReflectShader).
#[doc(hidden)]
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<T::Output, Self::Context>, ShaderCompileError>;
}
/// Marker trait for combinations of targets and compilations that can be reflected and compiled
/// successfully.
///
/// This trait is automatically implemented for reflected outputs that have [`FromCompilation`](crate::back::FromCompilation) implement
/// for a given target that also implement [`CompileShader`](crate::back::CompileShader) for that target.
/// This trait is automatically implemented for reflected outputs that have [`FromCompilation`] implement
/// for a given target that also implement [`CompileShader`] for that target.
pub trait CompileReflectShader<T: OutputTarget, C, S>:
CompileShader<
T,
@ -81,6 +94,13 @@ where
) -> Result<ShaderCompilerOutput<E::Output, Self::Context>, ShaderCompileError> {
self.backend.compile(options)
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<E::Output, Self::Context>, ShaderCompileError> {
self.backend.compile(options)
}
}
/// A trait for reflectable compilations that can be transformed
@ -122,6 +142,47 @@ where
) -> Result<ShaderReflection, ShaderReflectError> {
self.backend.reflect(pass_number, semantics)
}
fn validate(&mut self) -> Result<(), ShaderReflectError> {
self.backend.validate()
}
}
impl<T: ReflectShader + ?Sized> ReflectShader for Box<T> {
fn reflect(
&mut self,
pass_number: usize,
semantics: &ShaderSemantics,
) -> Result<ShaderReflection, ShaderReflectError> {
(**self).reflect(pass_number, semantics)
}
fn validate(&mut self) -> Result<(), ShaderReflectError> {
(**self).validate()
}
}
impl<O, T> CompileShader<T> for Box<O>
where
O: CompileShader<T> + ?Sized,
T: OutputTarget,
{
type Options = O::Options;
type Context = O::Context;
fn compile(
self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<T::Output, Self::Context>, ShaderCompileError> {
O::compile_boxed(self, options)
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<T::Output, Self::Context>, ShaderCompileError> {
self.compile(options)
}
}
#[cfg(test)]

View file

@ -1,16 +1,15 @@
use crate::back::targets::MSL;
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::back::{CompileReflectShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::cross::msl::MslReflect;
use crate::reflect::cross::{CompiledProgram, SpirvCross};
use crate::reflect::naga::{Naga, NagaReflect};
use crate::reflect::ReflectShader;
use naga::back::msl::TranslationInfo;
use naga::Module;
/// The HLSL shader model version to target.
pub use spirv_cross::msl::Version as MslVersion;
/// The MSL language version to target.
pub use spirv_cross2::compile::msl::MslVersion;
/// Compiler options for MSL
#[derive(Debug, Default, Clone)]
@ -22,15 +21,15 @@ pub struct MslNagaCompileOptions {
/// The context for a MSL compilation via spirv-cross.
pub struct CrossMslContext {
/// The compiled HLSL program.
pub artifact: CompiledProgram<spirv_cross::msl::Target>,
pub artifact: CompiledProgram<spirv_cross2::targets::Msl>,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, SpirvCross> for MSL {
type Target = MSL;
type Options = Option<self::MslVersion>;
type Context = CrossMslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
fn from_compilation(
compile: SpirvCompilation,
@ -41,6 +40,22 @@ impl FromCompilation<SpirvCompilation, SpirvCross> for MSL {
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, SpirvCross> for MSL {
type Target = MSL;
type Options = Option<self::MslVersion>;
type Context = CrossMslContext;
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: Box::new(MslReflect::try_from(&compile)?),
})
}
}
/// The naga module for a shader after compilation
pub struct NagaMslModule {
pub translation_info: TranslationInfo,
@ -53,12 +68,12 @@ pub struct NagaMslContext {
pub next_free_binding: u32,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, Naga> for MSL {
type Target = MSL;
type Options = Option<self::MslVersion>;
type Context = NagaMslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, Naga>;
fn from_compilation(
compile: SpirvCompilation,
@ -68,3 +83,19 @@ impl FromCompilation<SpirvCompilation, Naga> for MSL {
})
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, Naga> for MSL {
type Target = MSL;
type Options = Option<self::MslVersion>;
type Context = NagaMslContext;
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, Naga> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: Box::new(NagaReflect::try_from(&compile)?),
})
}
}

View file

@ -1,5 +1,7 @@
use crate::back::targets::SPIRV;
use crate::back::{CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput};
use crate::back::{
CompileReflectShader, CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput,
};
use crate::error::{ShaderCompileError, ShaderReflectError};
use crate::front::SpirvCompilation;
use crate::reflect::cross::glsl::GlslReflect;
@ -16,12 +18,12 @@ pub(crate) struct WriteSpirV {
pub(crate) fragment: Vec<u32>,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, SpirvCross> for SPIRV {
type Target = SPIRV;
type Options = Option<()>;
type Context = ();
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
fn from_compilation(
compile: SpirvCompilation,
@ -39,6 +41,29 @@ impl FromCompilation<SpirvCompilation, SpirvCross> for SPIRV {
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, SpirvCross> for SPIRV {
type Target = SPIRV;
type Options = Option<()>;
type Context = ();
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
let reflect = GlslReflect::try_from(&compile)?;
let vertex = compile.vertex;
let fragment = compile.fragment;
Ok(CompilerBackend {
backend: Box::new(WriteSpirV {
reflect,
vertex,
fragment,
}),
})
}
}
impl ReflectShader for WriteSpirV {
fn reflect(
&mut self,
@ -47,6 +72,10 @@ impl ReflectShader for WriteSpirV {
) -> Result<ShaderReflection, ShaderReflectError> {
self.reflect.reflect(pass_number, semantics)
}
fn validate(&mut self) -> Result<(), ShaderReflectError> {
self.reflect.validate()
}
}
impl CompileShader<SPIRV> for WriteSpirV {
@ -63,6 +92,17 @@ impl CompileShader<SPIRV> for WriteSpirV {
context: (),
})
}
fn compile_boxed(
self: Box<Self>,
_options: Self::Options,
) -> Result<ShaderCompilerOutput<Vec<u32>, Self::Context>, ShaderCompileError> {
Ok(ShaderCompilerOutput {
vertex: self.vertex,
fragment: self.fragment,
context: (),
})
}
}
/// The context for a SPIRV compilation via Naga
@ -71,12 +111,12 @@ pub struct NagaSpirvContext {
pub vertex: Module,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, Naga> for SPIRV {
type Target = SPIRV;
type Options = NagaSpirvOptions;
type Context = NagaSpirvContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, Naga>;
fn from_compilation(
compile: SpirvCompilation,
@ -87,6 +127,22 @@ impl FromCompilation<SpirvCompilation, Naga> for SPIRV {
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, Naga> for SPIRV {
type Target = SPIRV;
type Options = NagaSpirvOptions;
type Context = NagaSpirvContext;
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, Naga> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: Box::new(NagaReflect::try_from(&compile)?),
})
}
}
pub struct NagaSpirvOptions {
pub lowering: NagaLoweringOptions,
pub version: (u8, u8),

View file

@ -1,9 +1,8 @@
use crate::back::targets::WGSL;
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::back::{CompileReflectShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::naga::{Naga, NagaLoweringOptions, NagaReflect};
use crate::reflect::ReflectShader;
use naga::Module;
/// The context for a WGSL compilation via Naga
@ -12,12 +11,12 @@ pub struct NagaWgslContext {
pub vertex: Module,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, Naga> for WGSL {
type Target = WGSL;
type Options = NagaLoweringOptions;
type Context = NagaWgslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, Naga>;
fn from_compilation(
compile: SpirvCompilation,
@ -28,6 +27,22 @@ impl FromCompilation<SpirvCompilation, Naga> for WGSL {
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, Naga> for WGSL {
type Target = WGSL;
type Options = NagaLoweringOptions;
type Context = NagaWgslContext;
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, Naga> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: Box::new(NagaReflect::try_from(&compile)?),
})
}
}
#[cfg(test)]
mod test {
use crate::back::targets::WGSL;
@ -36,16 +51,19 @@ mod test {
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use crate::reflect::ReflectShader;
use bitflags::Flags;
use librashader_common::map::FastHashMap;
use librashader_common::map::{FastHashMap, ShortString};
use librashader_preprocess::ShaderSource;
#[test]
pub fn test_into() {
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
let result = ShaderSource::load("../test/basic.slang").unwrap();
let result =
ShaderSource::load("../test/shaders_slang/crt/shaders/slotmask.slang").unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let result = ShaderSource::load("../test/basic.slang").unwrap();
let mut uniform_semantics: FastHashMap<ShortString, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(

View file

@ -20,7 +20,7 @@ pub enum ShaderCompileError {
/// Error when transpiling from spirv-cross.
#[error("spirv-cross error: {0:?}")]
SpirvCrossCompileError(#[from] spirv_cross::ErrorCode),
SpirvCrossCompileError(#[from] spirv_cross2::SpirvCrossError),
/// Error when transpiling from spirv-to-dxil
#[cfg(all(target_os = "windows", feature = "dxil"))]
@ -87,7 +87,7 @@ pub enum SemanticsErrorKind {
pub enum ShaderReflectError {
/// Reflection error from spirv-cross.
#[error("spirv cross error: {0}")]
SpirvCrossError(#[from] spirv_cross::ErrorCode),
SpirvCrossError(#[from] spirv_cross2::SpirvCrossError),
/// Error when validating vertex shader semantics.
#[error("error when verifying vertex semantics: {0:?}")]
VertexSemanticError(SemanticsErrorKind),

View file

@ -1,5 +1,5 @@
use crate::error::ShaderCompileError;
use glslang::{CompilerOptions, ShaderInput};
use glslang::{CompilerOptions, ShaderInput, ShaderMessage};
use librashader_preprocess::ShaderSource;
use rspirv::binary::Assemble;
use rspirv::dr::Builder;
@ -25,14 +25,21 @@ pub(crate) fn compile_spirv(source: &ShaderSource) -> Result<SpirvCompilation, S
spirv_version: glslang::SpirvVersion::SPIRV1_0,
},
version_profile: None,
messages: ShaderMessage::DEFAULT,
};
let vertex = glslang::ShaderSource::from(source.vertex.as_str());
let vertex = ShaderInput::new(&vertex, glslang::ShaderStage::Vertex, &options, None)?;
let vertex = ShaderInput::new(&vertex, glslang::ShaderStage::Vertex, &options, None, None)?;
let vertex = compiler.create_shader(vertex)?;
let fragment = glslang::ShaderSource::from(source.fragment.as_str());
let fragment = ShaderInput::new(&fragment, glslang::ShaderStage::Fragment, &options, None)?;
let fragment = ShaderInput::new(
&fragment,
glslang::ShaderStage::Fragment,
&options,
None,
None,
)?;
let fragment = compiler.create_shader(fragment)?;
let vertex = vertex.compile()?;
@ -41,11 +48,12 @@ pub(crate) fn compile_spirv(source: &ShaderSource) -> Result<SpirvCompilation, S
let vertex = load_module(&vertex);
let fragment = load_module(&fragment);
let mut fragment = Builder::new_from_module(fragment);
let mut vertex = Builder::new_from_module(vertex);
let mut pass = link_input_outputs::LinkInputs::new(&vertex, &mut fragment);
let mut pass = link_input_outputs::LinkInputs::new(&mut vertex, &mut fragment, false);
pass.do_pass();
let vertex = vertex.assemble();
let vertex = vertex.module().assemble();
let fragment = fragment.module().assemble();
Ok(SpirvCompilation { vertex, fragment })

View file

@ -1,6 +1,5 @@
use crate::error::ShaderCompileError;
use librashader_preprocess::ShaderSource;
use serde::{Deserialize, Serialize};
pub(crate) mod spirv_passes;
mod glslang;
@ -25,20 +24,13 @@ impl ShaderReflectObject for SpirvCompilation {
}
/// A reflectable shader compilation via glslang.
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SpirvCompilation {
pub(crate) vertex: Vec<u32>,
pub(crate) fragment: Vec<u32>,
}
impl SpirvCompilation {
/// Tries to compile SPIR-V from the provided shader source.
pub fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError> {
glslang::compile_spirv(source)
}
}
impl TryFrom<&ShaderSource> for SpirvCompilation {
type Error = ShaderCompileError;

View file

@ -2,12 +2,21 @@ use rspirv::dr::{Builder, Module, Operand};
use rustc_hash::{FxHashMap, FxHashSet};
use spirv::{Decoration, Op, StorageClass};
/// Do DCE on inputs of the fragment shader, then
/// link by downgrading outputs of unused fragment inputs
/// to global variables on the vertex shader.
pub struct LinkInputs<'a> {
pub frag_builder: &'a mut Builder,
pub inputs: FxHashSet<spirv::Word>,
pub vert_builder: &'a mut Builder,
// binding -> ID
pub outputs: FxHashMap<u32, spirv::Word>,
// id -> binding
pub inputs_to_remove: FxHashMap<spirv::Word, u32>,
}
impl<'a> LinkInputs<'a> {
/// Get the value of the location of the inout in the module
fn find_location(module: &Module, id: spirv::Word) -> Option<u32> {
module.annotations.iter().find_map(|op| {
if op.class.opcode != Op::Decorate {
@ -33,9 +42,11 @@ impl<'a> LinkInputs<'a> {
})
}
pub fn new(vert: &'a Module, frag: &'a mut Builder) -> Self {
let mut inputs = FxHashSet::default();
let mut bindings = FxHashMap::default();
pub fn new(vert: &'a mut Builder, frag: &'a mut Builder, keep_if_bound: bool) -> Self {
let mut outputs = FxHashMap::default();
let mut inputs_to_remove = FxHashMap::default();
let mut inputs = FxHashMap::default();
for global in frag.module_ref().types_global_values.iter() {
if global.class.opcode == spirv::Op::Variable
&& global.operands[0] == Operand::StorageClass(StorageClass::Input)
@ -45,24 +56,32 @@ impl<'a> LinkInputs<'a> {
continue;
};
inputs.insert(id);
bindings.insert(location, id);
inputs_to_remove.insert(id, location);
inputs.insert(location, id);
}
}
}
for global in vert.types_global_values.iter() {
for global in vert.module_ref().types_global_values.iter() {
if global.class.opcode == spirv::Op::Variable
&& global.operands[0] == Operand::StorageClass(StorageClass::Output)
{
if let Some(id) = global.result_id {
let Some(location) = Self::find_location(vert, id) else {
let Some(location) = Self::find_location(vert.module_ref(), id) else {
continue;
};
if let Some(frag_ref) = bindings.get(&location) {
// if something is bound to the same location in the vertex shader,
// we're good.
inputs.remove(&frag_ref);
// Add to list of outputs
outputs.insert(location, id);
// Keep the input, if it is bound to both stages.Otherwise, do DCE analysis on
// the input, and remove it regardless if bound, if unused by the fragment stage.
if keep_if_bound {
if let Some(frag_ref) = inputs.get(&location) {
// if something is bound to the same location in the vertex shader,
// we're good.
inputs_to_remove.remove(&frag_ref);
}
}
}
}
@ -70,11 +89,189 @@ impl<'a> LinkInputs<'a> {
Self {
frag_builder: frag,
inputs,
vert_builder: vert,
outputs,
inputs_to_remove,
}
}
pub fn do_pass(&mut self) {
self.trim_inputs();
self.downgrade_outputs();
self.put_vertex_variables_to_end();
}
fn put_vertex_variables_to_end(&mut self) {
// this is easier than doing proper topo sort.
// we need it so that all type definitions are valid before
// being referred to by a variable.
let mut vars = Vec::new();
self.vert_builder
.module_mut()
.types_global_values
.retain(|instr| {
if instr.class.opcode == spirv::Op::Variable {
vars.push(instr.clone());
return false;
};
true
});
self.vert_builder
.module_mut()
.types_global_values
.append(&mut vars);
}
/// Downgrade dead inputs corresponding to outputs to global variables, keeping existing mappings.
fn downgrade_outputs(&mut self) {
let dead_outputs = self
.inputs_to_remove
.values()
.filter_map(|i| self.outputs.get(i).cloned())
.collect::<FxHashSet<spirv::Word>>();
let mut pointer_types_to_downgrade = FxHashSet::default();
// Map from Pointer type to pointee
let mut pointer_type_pointee = FxHashMap::default();
// Map from StorageClass Output to StorageClass Private
let mut downgraded_pointer_types = FxHashMap::default();
// First collect all the pointer types that are needed for dead outputs.
for global in self.vert_builder.module_ref().types_global_values.iter() {
if global.class.opcode != spirv::Op::Variable
|| global.operands[0] != Operand::StorageClass(StorageClass::Output)
{
continue;
}
if let Some(id) = global.result_id {
if !dead_outputs.contains(&id) {
continue;
}
if let Some(result_type) = global.result_type {
pointer_types_to_downgrade.insert(result_type);
}
}
}
// Collect all the pointee types of pointer types to downgrade
for global in self.vert_builder.module_ref().types_global_values.iter() {
if global.class.opcode != spirv::Op::TypePointer
|| global.operands[0] != Operand::StorageClass(StorageClass::Output)
{
continue;
}
if let Some(id) = global.result_id {
if !pointer_types_to_downgrade.contains(&id) {
continue;
}
let Some(pointee_type) = global.operands[1].id_ref_any() else {
continue;
};
pointer_type_pointee.insert(id, pointee_type);
}
}
// Create pointer types for everything we saw above with Private storage class.
// We don't have to deal with OpTypeForwardPointer, because PhysicalStorageBuffer
// is not valid in slang shaders, and we're only working with Vulkan inputs.
for (pointer_type, pointee_type) in pointer_type_pointee.iter() {
// Create a new private type
let private_pointer_type =
self.vert_builder
.type_pointer(None, StorageClass::Private, *pointee_type);
// Add it to the mapping
downgraded_pointer_types.insert(pointer_type, private_pointer_type);
}
// Downgrade the OpVariable storage class and reassign the types.
for global in self
.vert_builder
.module_mut()
.types_global_values
.iter_mut()
{
if global.class.opcode != spirv::Op::Variable
|| global.operands[0] != Operand::StorageClass(StorageClass::Output)
{
continue;
}
if let Some(id) = global.result_id {
if !dead_outputs.contains(&id) {
continue;
}
// downgrade the OpVariable if it's in dead_outputs
global.operands[0] = Operand::StorageClass(StorageClass::Private);
// Get the result type. If there's no result type it's invalid anyways
// so it doesn't matter that we downgraded early (better downgraded than unmatched)
let Some(result_type) = &mut global.result_type else {
continue;
};
let Some(new_type) = downgraded_pointer_types.get(&*result_type) else {
// We should have created one above.
continue;
};
// Set the type of the OpVariable to the same type with Private storageclass.
*result_type = *new_type;
}
}
// Strip decorations of downgraded variables.
self.vert_builder.module_mut().annotations.retain_mut(|op| {
if op.class.opcode != Op::Decorate {
return true;
}
let Some(Operand::Decoration(Decoration::Location)) = op.operands.get(1) else {
return true;
};
let Some(&Operand::IdRef(target)) = op.operands.get(0) else {
return true;
};
// If target is in dead outputs, then don't keep it.
!dead_outputs.contains(&target)
});
for entry_point in self.vert_builder.module_mut().entry_points.iter_mut() {
let mut index = 0;
entry_point.operands.retain(|s| {
// Skip the execution mode, entry point reference, and name.
if index < 3 {
index += 1;
return true;
}
index += 1;
// Ignore any non-IdRef
let Operand::IdRef(id_ref) = s else {
return true;
};
// If the entry point contains a dead outputs, remove it from the interface.
!dead_outputs.contains(id_ref)
});
}
}
// Trim unused fragment shader inputs
fn trim_inputs(&mut self) {
let functions = &self.frag_builder.module_ref().functions;
// literally if it has any reference at all we can keep it
@ -82,8 +279,8 @@ impl<'a> LinkInputs<'a> {
for param in &function.parameters {
for op in &param.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
self.inputs.remove(&word);
if self.inputs_to_remove.contains_key(&word) {
self.inputs_to_remove.remove(&word);
}
}
}
@ -93,8 +290,8 @@ impl<'a> LinkInputs<'a> {
for inst in &block.instructions {
for op in &inst.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
self.inputs.remove(&word);
if self.inputs_to_remove.contains_key(&word) {
self.inputs_to_remove.remove(&word);
}
}
}
@ -103,11 +300,10 @@ impl<'a> LinkInputs<'a> {
}
// ok well guess we dont
self.frag_builder.module_mut().debug_names.retain(|instr| {
for op in &instr.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
if self.inputs_to_remove.contains_key(&word) {
return false;
}
}
@ -118,7 +314,7 @@ impl<'a> LinkInputs<'a> {
self.frag_builder.module_mut().annotations.retain(|instr| {
for op in &instr.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
if self.inputs_to_remove.contains_key(&word) {
return false;
}
}
@ -129,7 +325,7 @@ impl<'a> LinkInputs<'a> {
for entry_point in self.frag_builder.module_mut().entry_points.iter_mut() {
entry_point.operands.retain(|op| {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
if self.inputs_to_remove.contains_key(&word) {
return false;
}
}
@ -145,7 +341,7 @@ impl<'a> LinkInputs<'a> {
return true;
};
!self.inputs.contains(&id)
!self.inputs_to_remove.contains_key(&id)
});
}
}

View file

@ -34,7 +34,7 @@
//! pub fn compile_preset(preset: ShaderPreset) -> Result<(Vec<ShaderPassMeta>, ShaderSemantics), Box<dyn Error>>
//! {
//! let (passes, semantics) = SPIRV::compile_preset_passes::<SpirvCompilation, SpirvCross, Box<dyn Error>>(
//! preset.shaders, &preset.textures)?;
//! preset.passes, &preset.textures)?;
//! Ok((passes, semantics))
//! }
//! ```
@ -43,9 +43,7 @@
//! librashader-reflect is designed to be compiler-agnostic. [naga](https://docs.rs/naga/latest/naga/index.html),
//! a pure-Rust shader compiler, as well as SPIRV-Cross via [SpirvCompilation](crate::front::SpirvCompilation)
//! is supported.
#![feature(impl_trait_in_assoc_type)]
#![feature(let_chains)]
#![cfg_attr(not(feature = "stable"), feature(impl_trait_in_assoc_type))]
/// Shader codegen backends.
pub mod back;
/// Error types.

View file

@ -2,138 +2,173 @@ use crate::back::glsl::CrossGlslContext;
use crate::back::targets::GLSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
use spirv_cross::spirv::Decoration;
use spirv_cross::ErrorCode;
use crate::reflect::cross::{CompiledProgram, CrossReflect};
use spirv::Decoration;
pub(crate) type GlslReflect = CrossReflect<spirv_cross::glsl::Target>;
use spirv_cross2::compile::CompilableTarget;
use spirv_cross2::reflect::{DecorationValue, ResourceType};
use spirv_cross2::{targets, SpirvCrossError};
impl CompileShader<GLSL> for CrossReflect<spirv_cross::glsl::Target> {
type Options = spirv_cross::glsl::Version;
pub(crate) type GlslReflect = CrossReflect<targets::Glsl>;
impl CompileShader<GLSL> for CrossReflect<targets::Glsl> {
type Options = spirv_cross2::compile::glsl::GlslVersion;
type Context = CrossGlslContext;
fn compile(
mut self,
version: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
let mut options: spirv_cross::glsl::CompilerOptions = Default::default();
let mut options = targets::Glsl::options();
options.version = version;
options.fragment.default_float_precision = spirv_cross::glsl::Precision::High;
options.fragment.default_int_precision = spirv_cross::glsl::Precision::High;
options.enable_420_pack_extension = false;
self.vertex.set_compiler_options(&options)?;
self.fragment.set_compiler_options(&options)?;
options.es_default_float_precision_highp = true;
options.es_default_int_precision_highp = true;
options.enable_420pack_extension = false;
let vertex_resources = self.vertex.get_shader_resources()?;
let fragment_resources = self.fragment.get_shader_resources()?;
let vertex_resources = self.vertex.shader_resources()?;
let fragment_resources = self.fragment.shader_resources()?;
for res in &vertex_resources.stage_outputs {
for res in vertex_resources.resources_for_type(ResourceType::StageOutput)? {
// let location = self.vertex.get_decoration(res.id, Decoration::Location)?;
// self.vertex
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
self.vertex.unset_decoration(res.id, Decoration::Location)?;
self.vertex
.set_decoration(res.id, Decoration::Location, DecorationValue::unset())?;
}
for res in &fragment_resources.stage_inputs {
for res in fragment_resources.resources_for_type(ResourceType::StageInput)? {
// let location = self.fragment.get_decoration(res.id, Decoration::Location)?;
// self.fragment
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
self.fragment
.unset_decoration(res.id, Decoration::Location)?;
.set_decoration(res.id, Decoration::Location, DecorationValue::unset())?;
}
if vertex_resources.push_constant_buffers.len() > 1 {
let vertex_pcb = vertex_resources.resources_for_type(ResourceType::PushConstant)?;
if vertex_pcb.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
SpirvCrossError::InvalidArgument(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
for res in &vertex_resources.push_constant_buffers {
self.vertex.set_name(res.id, "LIBRA_PUSH_VERTEX_INSTANCE")?;
for res in vertex_pcb {
self.vertex
.set_name(res.base_type_id, "LIBRA_PUSH_VERTEX")?;
.set_name(res.id, c"LIBRA_PUSH_VERTEX_INSTANCE")?;
self.vertex
.set_name(res.base_type_id, c"LIBRA_PUSH_VERTEX")?;
}
// todo: options
let _flatten = false;
if vertex_resources.uniform_buffers.len() > 1 {
let vertex_ubo = vertex_resources.resources_for_type(ResourceType::UniformBuffer)?;
if vertex_ubo.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
SpirvCrossError::InvalidArgument(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
for res in &vertex_resources.uniform_buffers {
for res in vertex_ubo {
// if flatten {
// self.vertex.flatten_buffer_block(res.id)?;
// }
self.vertex.set_name(res.id, "LIBRA_UBO_VERTEX_INSTANCE")?;
self.vertex.set_name(res.base_type_id, "LIBRA_UBO_VERTEX")?;
self.vertex.set_name(res.id, c"LIBRA_UBO_VERTEX_INSTANCE")?;
self.vertex
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.vertex.unset_decoration(res.id, Decoration::Binding)?;
.set_name(res.base_type_id, c"LIBRA_UBO_VERTEX")?;
self.vertex.set_decoration(
res.id,
Decoration::DescriptorSet,
DecorationValue::unset(),
)?;
self.vertex
.set_decoration(res.id, Decoration::Binding, DecorationValue::unset())?;
}
if fragment_resources.push_constant_buffers.len() > 1 {
let fragment_pcb = fragment_resources.resources_for_type(ResourceType::PushConstant)?;
if fragment_pcb.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
SpirvCrossError::InvalidArgument(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
for res in &fragment_resources.push_constant_buffers {
for res in fragment_pcb {
self.fragment
.set_name(res.id, "LIBRA_PUSH_FRAGMENT_INSTANCE")?;
.set_name(res.id, c"LIBRA_PUSH_FRAGMENT_INSTANCE")?;
self.fragment
.set_name(res.base_type_id, "LIBRA_PUSH_FRAGMENT")?;
.set_name(res.base_type_id, c"LIBRA_PUSH_FRAGMENT")?;
}
if fragment_resources.uniform_buffers.len() > 1 {
let fragment_ubo = fragment_resources.resources_for_type(ResourceType::UniformBuffer)?;
if fragment_ubo.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
SpirvCrossError::InvalidArgument(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
for res in &fragment_resources.uniform_buffers {
for res in fragment_ubo {
// if flatten {
// self.fragment.flatten_buffer_block(res.id)?;
// }
self.fragment
.set_name(res.id, "LIBRA_UBO_FRAGMENT_INSTANCE")?;
.set_name(res.id, c"LIBRA_UBO_FRAGMENT_INSTANCE")?;
self.fragment
.set_name(res.base_type_id, "LIBRA_UBO_FRAGMENT")?;
.set_name(res.base_type_id, c"LIBRA_UBO_FRAGMENT")?;
self.fragment.set_decoration(
res.id,
Decoration::DescriptorSet,
DecorationValue::unset(),
)?;
self.fragment
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.fragment
.unset_decoration(res.id, Decoration::Binding)?;
.set_decoration(res.id, Decoration::Binding, DecorationValue::unset())?;
}
let mut texture_fixups = Vec::new();
for res in fragment_resources.sampled_images {
let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?;
for res in fragment_resources.resources_for_type(ResourceType::SampledImage)? {
let Some(DecorationValue::Literal(binding)) =
self.fragment.decoration(res.id, Decoration::Binding)?
else {
continue;
};
self.fragment.set_decoration(
res.id,
Decoration::DescriptorSet,
DecorationValue::unset(),
)?;
self.fragment
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.fragment
.unset_decoration(res.id, Decoration::Binding)?;
let mut name = res.name;
name.push('\0');
.set_decoration(res.id, Decoration::Binding, DecorationValue::unset())?;
let name = res.name.to_string();
texture_fixups.push((name, binding));
}
let vertex_compiled = self.vertex.compile(&options)?;
let fragment_compiled = self.fragment.compile(&options)?;
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
vertex: vertex_compiled.to_string(),
fragment: fragment_compiled.to_string(),
context: CrossGlslContext {
sampler_bindings: texture_fixups,
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
vertex: vertex_compiled,
fragment: fragment_compiled,
},
},
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
<CrossReflect<targets::Glsl> as CompileShader<GLSL>>::compile(*self, options)
}
}

View file

@ -2,14 +2,17 @@ use crate::back::hlsl::{CrossHlslContext, HlslBufferAssignment, HlslBufferAssign
use crate::back::targets::HLSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
use spirv_cross::hlsl::ShaderModel as HlslShaderModel;
use spirv_cross::spirv::Decoration;
use spirv_cross::ErrorCode;
use crate::reflect::cross::{CompiledProgram, CrossReflect};
use spirv::Decoration;
pub(crate) type HlslReflect = CrossReflect<spirv_cross::hlsl::Target>;
use spirv_cross2::compile::hlsl::HlslShaderModel;
use spirv_cross2::compile::CompilableTarget;
use spirv_cross2::reflect::{DecorationValue, ResourceType};
use spirv_cross2::{targets, SpirvCrossError};
impl CompileShader<HLSL> for CrossReflect<spirv_cross::hlsl::Target> {
pub(crate) type HlslReflect = CrossReflect<targets::Hlsl>;
impl CompileShader<HLSL> for CrossReflect<targets::Hlsl> {
type Options = Option<HlslShaderModel>;
type Context = CrossHlslContext;
@ -17,98 +20,107 @@ impl CompileShader<HLSL> for CrossReflect<spirv_cross::hlsl::Target> {
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, CrossHlslContext>, ShaderCompileError> {
let sm = options.unwrap_or(HlslShaderModel::V5_0);
let mut options = spirv_cross::hlsl::CompilerOptions::default();
options.shader_model = sm;
let sm = options.unwrap_or(HlslShaderModel::ShaderModel5_0);
self.vertex.set_compiler_options(&options)?;
self.fragment.set_compiler_options(&options)?;
let mut options = targets::Hlsl::options();
options.shader_model = sm;
// todo: options
let vertex_resources = self.vertex.get_shader_resources()?;
let fragment_resources = self.fragment.get_shader_resources()?;
let vertex_resources = self.vertex.shader_resources()?;
let fragment_resources = self.fragment.shader_resources()?;
let mut vertex_buffer_assignment = HlslBufferAssignments::default();
let mut fragment_buffer_assignment = HlslBufferAssignments::default();
if vertex_resources.uniform_buffers.len() > 1 {
let mut vertex_ubo = vertex_resources.resources_for_type(ResourceType::UniformBuffer)?;
if vertex_ubo.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
SpirvCrossError::InvalidArgument(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
if let Some(buf) = vertex_resources.uniform_buffers.first() {
if let Some(buf) = vertex_ubo.next() {
vertex_buffer_assignment.ubo = Some(HlslBufferAssignment {
name: buf.name.clone(),
id: buf.id,
name: buf.name.to_string(),
id: buf.id.id(),
})
}
if vertex_resources.push_constant_buffers.len() > 1 {
let mut vertex_pcb = vertex_resources.resources_for_type(ResourceType::PushConstant)?;
if vertex_pcb.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
SpirvCrossError::InvalidArgument(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
if let Some(buf) = vertex_resources.push_constant_buffers.first() {
if let Some(buf) = vertex_pcb.next() {
vertex_buffer_assignment.push = Some(HlslBufferAssignment {
name: buf.name.clone(),
id: buf.id,
name: buf.name.to_string(),
id: buf.id.id(),
})
}
if fragment_resources.uniform_buffers.len() > 1 {
let mut fragment_ubo =
fragment_resources.resources_for_type(ResourceType::UniformBuffer)?;
if fragment_ubo.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
SpirvCrossError::InvalidArgument(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
if let Some(buf) = fragment_resources.uniform_buffers.first() {
if let Some(buf) = fragment_ubo.next() {
fragment_buffer_assignment.ubo = Some(HlslBufferAssignment {
name: buf.name.clone(),
id: buf.id,
name: buf.name.to_string(),
id: buf.id.id(),
})
}
if fragment_resources.push_constant_buffers.len() > 1 {
let mut fragment_pcb = fragment_resources.resources_for_type(ResourceType::PushConstant)?;
if fragment_pcb.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
SpirvCrossError::InvalidArgument(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
if let Some(buf) = fragment_resources.push_constant_buffers.first() {
if let Some(buf) = fragment_pcb.next() {
fragment_buffer_assignment.push = Some(HlslBufferAssignment {
name: buf.name.clone(),
id: buf.id,
name: buf.name.to_string(),
id: buf.id.id(),
})
}
if sm == HlslShaderModel::V3_0 {
for res in &fragment_resources.sampled_images {
let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?;
if sm == HlslShaderModel::ShaderModel3_0 {
for res in fragment_resources.resources_for_type(ResourceType::SampledImage)? {
let Some(DecorationValue::Literal(binding)) =
self.fragment.decoration(res.id, Decoration::Binding)?
else {
continue;
};
self.fragment
.set_name(res.id, &format!("LIBRA_SAMPLER2D_{binding}"))?;
.set_name(res.id, format!("LIBRA_SAMPLER2D_{binding}"))?;
// self.fragment
// .unset_decoration(res.id, Decoration::Binding)?;
}
}
let vertex_compiled = self.vertex.compile(&options)?;
let fragment_compiled = self.fragment.compile(&options)?;
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
vertex: vertex_compiled.to_string(),
fragment: fragment_compiled.to_string(),
context: CrossHlslContext {
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
vertex: vertex_compiled,
fragment: fragment_compiled,
},
vertex_buffers: vertex_buffer_assignment,
@ -116,4 +128,11 @@ impl CompileShader<HLSL> for CrossReflect<spirv_cross::hlsl::Target> {
},
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
<CrossReflect<targets::Hlsl> as CompileShader<HLSL>>::compile(*self, options)
}
}

View file

@ -9,6 +9,7 @@ pub mod msl;
use crate::error::{SemanticsErrorKind, ShaderReflectError};
use crate::front::SpirvCompilation;
use crate::reflect::helper::{SemanticErrorBlame, TextureData, UboData};
use crate::reflect::semantics::{
BindingMeta, BindingStage, BufferReflection, MemberOffset, ShaderReflection, ShaderSemantics,
TextureBinding, TextureSemanticMap, TextureSemantics, TextureSizeMeta, TypeInfo,
@ -16,13 +17,15 @@ use crate::reflect::semantics::{
MAX_BINDINGS_COUNT, MAX_PUSH_BUFFER_SIZE,
};
use crate::reflect::{align_uniform_size, ReflectShader};
use librashader_common::map::ShortString;
use spirv_cross2::compile::CompiledArtifact;
use spirv_cross2::reflect::{
AllResources, BitWidth, DecorationValue, Resource, Scalar, ScalarKind, TypeInner,
};
use spirv_cross2::spirv::Decoration;
use spirv_cross2::Compiler;
use spirv_cross2::Module;
use std::fmt::Debug;
use std::ops::Deref;
use spirv_cross::spirv::{Ast, Decoration, Module, Resource, ShaderResources, Type};
use spirv_cross::ErrorCode;
use crate::reflect::helper::{SemanticErrorBlame, TextureData, UboData};
/// Reflect shaders under SPIRV-Cross semantics.
///
@ -30,135 +33,115 @@ use crate::reflect::helper::{SemanticErrorBlame, TextureData, UboData};
#[derive(Debug)]
pub struct SpirvCross;
// This is "probably" OK.
unsafe impl<T: Send + spirv_cross::spirv::Target> Send for CrossReflect<T>
where
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
{
}
// todo: make this under a mutex
pub(crate) struct CrossReflect<T>
where
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
T: spirv_cross2::compile::CompilableTarget,
{
vertex: Ast<T>,
fragment: Ast<T>,
}
///The output of the SPIR-V AST after compilation.
///
/// This output is immutable and can not be recompiled later.
pub struct CompiledAst<T: spirv_cross::spirv::Target>(Ast<T>);
impl<T: spirv_cross::spirv::Target> Deref for CompiledAst<T> {
type Target = Ast<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
vertex: Compiler<T>,
fragment: Compiler<T>,
}
/// The compiled SPIR-V program after compilation.
pub struct CompiledProgram<T>
where
T: spirv_cross::spirv::Target,
T: spirv_cross2::compile::CompilableTarget,
{
pub vertex: CompiledAst<T>,
pub fragment: CompiledAst<T>,
pub vertex: CompiledArtifact<T>,
pub fragment: CompiledArtifact<T>,
}
impl ValidateTypeSemantics<Type> for UniqueSemantics {
fn validate_type(&self, ty: &Type) -> Option<TypeInfo> {
let (Type::Float {
ref array,
vecsize,
columns,
..
}
| Type::Int {
ref array,
vecsize,
columns,
..
}
| Type::UInt {
ref array,
vecsize,
columns,
..
}) = *ty
impl ValidateTypeSemantics<TypeInner<'_>> for UniqueSemantics {
fn validate_type(&self, ty: &TypeInner) -> Option<TypeInfo> {
let (TypeInner::Vector { .. } | TypeInner::Scalar { .. } | TypeInner::Matrix { .. }) = *ty
else {
return None;
};
if !array.is_empty() {
return None;
}
let valid = match self {
match self {
UniqueSemantics::MVP => {
matches!(ty, Type::Float { .. }) && vecsize == 4 && columns == 4
if matches!(ty, TypeInner::Matrix { columns, rows, scalar: Scalar { size, .. } } if *columns == 4
&& *rows == 4 && *size == BitWidth::Word)
{
return Some(TypeInfo {
size: 4,
columns: 4,
});
}
}
UniqueSemantics::FrameCount
| UniqueSemantics::Rotation
| UniqueSemantics::TotalSubFrames
| UniqueSemantics::CurrentSubFrame => {
matches!(ty, Type::UInt { .. }) && vecsize == 1 && columns == 1
| UniqueSemantics::CurrentSubFrame
| UniqueSemantics::TotalSubFrames => {
// Uint32 == width 4
if matches!(ty, TypeInner::Scalar( Scalar { kind, size }) if *kind == ScalarKind::Uint && *size == BitWidth::Word)
{
return Some(TypeInfo {
size: 1,
columns: 1,
});
}
}
UniqueSemantics::FrameDirection => {
matches!(ty, Type::Int { .. }) && vecsize == 1 && columns == 1
// iint32 == width 4
if matches!(ty, TypeInner::Scalar( Scalar { kind, size }) if *kind == ScalarKind::Int && *size == BitWidth::Word)
{
return Some(TypeInfo {
size: 1,
columns: 1,
});
}
}
UniqueSemantics::FloatParameter => {
matches!(ty, Type::Float { .. }) && vecsize == 1 && columns == 1
// Float32 == width 4
if matches!(ty, TypeInner::Scalar( Scalar { kind, size }) if *kind == ScalarKind::Float && *size == BitWidth::Word)
{
return Some(TypeInfo {
size: 1,
columns: 1,
});
}
}
_ => {
if matches!(ty, TypeInner::Vector { scalar: Scalar { size, kind }, width: vecwidth, .. }
if *kind == ScalarKind::Float && *size == BitWidth::Word && *vecwidth == 4)
{
return Some(TypeInfo {
size: 4,
columns: 1,
});
}
}
_ => matches!(ty, Type::Float { .. }) && vecsize == 4 && columns == 1,
};
if valid {
Some(TypeInfo {
size: vecsize,
columns,
})
} else {
None
}
None
}
}
impl ValidateTypeSemantics<Type> for TextureSemantics {
fn validate_type(&self, ty: &Type) -> Option<TypeInfo> {
let Type::Float {
ref array,
vecsize,
columns,
..
} = *ty
impl ValidateTypeSemantics<TypeInner<'_>> for TextureSemantics {
fn validate_type(&self, ty: &TypeInner) -> Option<TypeInfo> {
let TypeInner::Vector {
scalar: Scalar { size, kind },
width: vecwidth,
} = ty
else {
return None;
};
if !array.is_empty() {
return None;
if *kind == ScalarKind::Float && *size == BitWidth::Word && *vecwidth == 4 {
return Some(TypeInfo {
size: 4,
columns: 1,
});
}
if vecsize == 4 && columns == 1 {
Some(TypeInfo {
size: vecsize,
columns,
})
} else {
None
}
None
}
}
impl<T> TryFrom<&SpirvCompilation> for CrossReflect<T>
where
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
T: spirv_cross2::compile::CompilableTarget,
{
type Error = ShaderReflectError;
@ -166,8 +149,8 @@ where
let vertex_module = Module::from_words(&value.vertex);
let fragment_module = Module::from_words(&value.fragment);
let vertex = Ast::parse(&vertex_module)?;
let fragment = Ast::parse(&fragment_module)?;
let vertex = Compiler::new(vertex_module)?;
let fragment = Compiler::new(fragment_module)?;
Ok(CrossReflect { vertex, fragment })
}
@ -175,14 +158,12 @@ where
impl<T> CrossReflect<T>
where
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
T: spirv_cross2::compile::CompilableTarget,
{
fn validate(
fn validate_semantics(
&self,
vertex_res: &ShaderResources,
fragment_res: &ShaderResources,
vertex_res: &AllResources,
fragment_res: &AllResources,
) -> Result<(), ShaderReflectError> {
if !vertex_res.sampled_images.is_empty()
|| !vertex_res.storage_buffers.is_empty()
@ -219,9 +200,15 @@ where
));
}
let fragment_location = self
let Some(DecorationValue::Literal(fragment_location)) = self
.fragment
.get_decoration(fragment_res.stage_outputs[0].id, Decoration::Location)?;
.decoration(fragment_res.stage_outputs[0].id, Decoration::Location)?
else {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
if fragment_location != 0 {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidLocation(fragment_location),
@ -229,15 +216,54 @@ where
}
// Ensure that vertex attributes use location 0 and 1
let vert_mask = vertex_res.stage_inputs.iter().try_fold(0, |mask, input| {
Ok::<u32, ErrorCode>(
mask | 1 << self.vertex.get_decoration(input.id, Decoration::Location)?,
)
})?;
if vert_mask != 0x3 {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidLocation(vert_mask),
));
// Verify Vertex inputs
'vertex: {
let entry_points = self.vertex.entry_points()?;
if entry_points.len() != 1 {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidEntryPointCount(entry_points.len()),
));
}
let vert_inputs = vertex_res.stage_inputs.len();
if vert_inputs != 2 {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidInputCount(vert_inputs),
));
}
for input in &vertex_res.stage_inputs {
let location = self.vertex.decoration(input.id, Decoration::Location)?;
let Some(DecorationValue::Literal(location)) = location else {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
if location == 0 {
let pos_type = &self.vertex.type_description(input.base_type_id)?;
if !matches!(pos_type.inner, TypeInner::Vector { width, ..} if width == 4) {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidLocation(location),
));
}
break 'vertex;
}
if location == 1 {
let coord_type = &self.vertex.type_description(input.base_type_id)?;
if !matches!(coord_type.inner, TypeInner::Vector { width, ..} if width == 2) {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidLocation(location),
));
}
break 'vertex;
}
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidLocation(location),
));
}
}
if vertex_res.uniform_buffers.len() > 1 {
@ -271,17 +297,27 @@ where
impl<T> CrossReflect<T>
where
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
T: spirv_cross2::compile::CompilableTarget,
{
fn get_ubo_data(
ast: &Ast<T>,
ast: &Compiler<T>,
ubo: &Resource,
blame: SemanticErrorBlame,
) -> Result<UboData, ShaderReflectError> {
let descriptor_set = ast.get_decoration(ubo.id, Decoration::DescriptorSet)?;
let binding = ast.get_decoration(ubo.id, Decoration::Binding)?;
let Some(descriptor_set) = ast
.decoration(ubo.id, Decoration::DescriptorSet)?
.and_then(|l| l.as_literal())
else {
return Err(blame.error(SemanticsErrorKind::MissingBinding));
};
let Some(binding) = ast
.decoration(ubo.id, Decoration::Binding)?
.and_then(|l| l.as_literal())
else {
return Err(blame.error(SemanticsErrorKind::MissingBinding));
};
if binding >= MAX_BINDINGS_COUNT {
return Err(blame.error(SemanticsErrorKind::InvalidBinding(binding)));
}
@ -289,21 +325,19 @@ where
return Err(blame.error(SemanticsErrorKind::InvalidDescriptorSet(descriptor_set)));
}
let size = ast.get_declared_struct_size(ubo.base_type_id)?;
Ok(UboData {
// descriptor_set,
// id: ubo.id,
binding,
size,
})
let size = ast.type_description(ubo.base_type_id)?.size_hint.declared() as u32;
Ok(UboData { binding, size })
}
fn get_push_size(
ast: &Ast<T>,
ast: &Compiler<T>,
push: &Resource,
blame: SemanticErrorBlame,
) -> Result<u32, ShaderReflectError> {
let size = ast.get_declared_struct_size(push.base_type_id)?;
let size = ast
.type_description(push.base_type_id)?
.size_hint
.declared() as u32;
if size > MAX_PUSH_BUFFER_SIZE {
return Err(blame.error(SemanticsErrorKind::InvalidPushBufferSize(size)));
}
@ -311,7 +345,7 @@ where
}
fn reflect_buffer_range_metas(
ast: &Ast<T>,
ast: &Compiler<T>,
resource: &Resource,
pass_number: usize,
semantics: &ShaderSemantics,
@ -319,35 +353,43 @@ where
offset_type: UniformMemberBlock,
blame: SemanticErrorBlame,
) -> Result<(), ShaderReflectError> {
let ranges = ast.get_active_buffer_ranges(resource.id)?;
let ranges = ast.active_buffer_ranges(resource.id)?;
for range in ranges {
let name = ast.get_member_name(resource.base_type_id, range.index)?;
let ubo_type = ast.get_type(resource.base_type_id)?;
let range_type = match ubo_type {
Type::Struct { member_types, .. } => {
let range_type = member_types
let Some(name) = ast.member_name(resource.base_type_id, range.index)? else {
// member has no name!
return Err(blame.error(SemanticsErrorKind::InvalidRange(range.index)));
};
let ubo_type = ast.type_description(resource.base_type_id)?;
let range_type = match ubo_type.inner {
TypeInner::Struct(struct_def) => {
let range_type = struct_def
.members
.get(range.index as usize)
.cloned()
.ok_or(blame.error(SemanticsErrorKind::InvalidRange(range.index)))?;
ast.get_type(range_type)?
ast.type_description(range_type.id)?
}
_ => return Err(blame.error(SemanticsErrorKind::InvalidResourceType)),
};
if let Some(parameter) = semantics.uniform_semantics.get_unique_semantic(&name) {
let Some(typeinfo) = parameter.semantics.validate_type(&range_type) else {
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)));
if let Some(parameter) = semantics.uniform_semantics.unique_semantic(&name) {
let Some(typeinfo) = parameter.semantics.validate_type(&range_type.inner) else {
return Err(
blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name.to_string()))
);
};
match &parameter.semantics {
UniqueSemantics::FloatParameter => {
let offset = range.offset;
if let Some(meta) = meta.parameter_meta.get_mut(&name) {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset
if let Some(meta) = meta.parameter_meta.get_mut::<str>(&name.as_ref()) {
if let Some(expected) = meta
.offset
.offset(offset_type)
.filter(|expected| *expected != offset)
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
semantic: name.to_string(),
expected,
received: offset,
ty: offset_type,
@ -356,7 +398,7 @@ where
}
if meta.size != typeinfo.size {
return Err(ShaderReflectError::MismatchedSize {
semantic: name,
semantic: name.to_string(),
vertex: meta.size,
fragment: typeinfo.size,
pass: pass_number,
@ -365,6 +407,7 @@ where
*meta.offset.offset_mut(offset_type) = Some(offset);
} else {
let name = ShortString::from(name.as_ref());
meta.parameter_meta.insert(
name.clone(),
VariableMeta {
@ -378,11 +421,13 @@ where
semantics => {
let offset = range.offset;
if let Some(meta) = meta.unique_meta.get_mut(semantics) {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset
if let Some(expected) = meta
.offset
.offset(offset_type)
.filter(|expected| *expected != offset)
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
semantic: name.to_string(),
expected,
received: offset,
ty: offset_type,
@ -391,7 +436,7 @@ where
}
if meta.size != typeinfo.size * typeinfo.columns {
return Err(ShaderReflectError::MismatchedSize {
semantic: name,
semantic: name.to_string(),
vertex: meta.size,
fragment: typeinfo.size,
pass: pass_number,
@ -403,7 +448,7 @@ where
meta.unique_meta.insert(
*semantics,
VariableMeta {
id: name,
id: ShortString::from(name.as_ref()),
offset: MemberOffset::new(offset, offset_type),
size: typeinfo.size * typeinfo.columns,
},
@ -411,9 +456,11 @@ where
}
}
}
} else if let Some(texture) = semantics.uniform_semantics.get_texture_semantic(&name) {
let Some(_typeinfo) = texture.semantics.validate_type(&range_type) else {
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)));
} else if let Some(texture) = semantics.uniform_semantics.texture_semantic(&name) {
let Some(_typeinfo) = texture.semantics.validate_type(&range_type.inner) else {
return Err(
blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name.to_string()))
);
};
if let TextureSemantics::PassOutput = texture.semantics {
@ -427,11 +474,13 @@ where
let offset = range.offset;
if let Some(meta) = meta.texture_size_meta.get_mut(&texture) {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset
if let Some(expected) = meta
.offset
.offset(offset_type)
.filter(|expected| *expected != offset)
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
semantic: name.to_string(),
expected,
received: offset,
ty: offset_type,
@ -454,12 +503,12 @@ where
SemanticErrorBlame::Vertex => BindingStage::VERTEX,
SemanticErrorBlame::Fragment => BindingStage::FRAGMENT,
},
id: name,
id: ShortString::from(name.as_ref()),
},
);
}
} else {
return Err(blame.error(SemanticsErrorKind::UnknownSemantics(name)));
return Err(blame.error(SemanticsErrorKind::UnknownSemantics(name.to_string())));
}
}
Ok(())
@ -472,12 +521,12 @@ where
) -> Result<Option<BufferReflection<u32>>, ShaderReflectError> {
if let Some(vertex_ubo) = vertex_ubo {
self.vertex
.set_decoration(vertex_ubo.id, Decoration::Binding, 0)?;
.set_decoration(vertex_ubo.id, Decoration::Binding, Some(0))?;
}
if let Some(fragment_ubo) = fragment_ubo {
self.fragment
.set_decoration(fragment_ubo.id, Decoration::Binding, 0)?;
.set_decoration(fragment_ubo.id, Decoration::Binding, Some(0))?;
}
match (vertex_ubo, fragment_ubo) {
@ -529,10 +578,7 @@ where
semantics: &ShaderSemantics,
meta: &mut BindingMeta,
) -> Result<(), ShaderReflectError> {
let Some(semantic) = semantics
.texture_semantics
.get_texture_semantic(texture.name)
else {
let Some(semantic) = semantics.texture_semantics.texture_semantic(texture.name) else {
return Err(
SemanticErrorBlame::Fragment.error(SemanticsErrorKind::UnknownSemantics(
texture.name.to_string(),
@ -560,12 +606,25 @@ where
&'a self,
texture: &'a Resource,
) -> Result<TextureData<'a>, ShaderReflectError> {
let descriptor_set = self
let Some(descriptor_set) = self
.fragment
.get_decoration(texture.id, Decoration::DescriptorSet)?;
let binding = self
.decoration(texture.id, Decoration::DescriptorSet)?
.and_then(|l| l.as_literal())
else {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
let Some(binding) = self
.fragment
.get_decoration(texture.id, Decoration::Binding)?;
.decoration(texture.id, Decoration::Binding)?
.and_then(|l| l.as_literal())
else {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
if descriptor_set != 0 {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidDescriptorSet(descriptor_set),
@ -592,12 +651,12 @@ where
) -> Result<Option<BufferReflection<Option<u32>>>, ShaderReflectError> {
if let Some(vertex_pcb) = vertex_pcb {
self.vertex
.set_decoration(vertex_pcb.id, Decoration::Binding, 1)?;
.set_decoration(vertex_pcb.id, Decoration::Binding, Some(1))?;
}
if let Some(fragment_pcb) = fragment_pcb {
self.fragment
.set_decoration(fragment_pcb.id, Decoration::Binding, 1)?;
.set_decoration(fragment_pcb.id, Decoration::Binding, Some(1))?;
}
match (vertex_pcb, fragment_pcb) {
@ -646,18 +705,16 @@ where
impl<T> ReflectShader for CrossReflect<T>
where
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
T: spirv_cross2::compile::CompilableTarget,
{
fn reflect(
&mut self,
pass_number: usize,
semantics: &ShaderSemantics,
) -> Result<ShaderReflection, ShaderReflectError> {
let vertex_res = self.vertex.get_shader_resources()?;
let fragment_res = self.fragment.get_shader_resources()?;
self.validate(&vertex_res, &fragment_res)?;
let vertex_res = self.vertex.shader_resources()?.all_resources()?;
let fragment_res = self.fragment.shader_resources()?.all_resources()?;
self.validate_semantics(&vertex_res, &fragment_res)?;
let vertex_ubo = vertex_res.uniform_buffers.first();
let fragment_ubo = fragment_res.uniform_buffers.first();
@ -740,6 +797,23 @@ where
meta,
})
}
fn validate(&mut self) -> Result<(), ShaderReflectError> {
let vertex_res = self.vertex.shader_resources()?.all_resources()?;
let fragment_res = self.fragment.shader_resources()?.all_resources()?;
self.validate_semantics(&vertex_res, &fragment_res)?;
let vertex_ubo = vertex_res.uniform_buffers.first();
let fragment_ubo = fragment_res.uniform_buffers.first();
self.reflect_ubos(vertex_ubo, fragment_ubo)?;
let vertex_push = vertex_res.push_constant_buffers.first();
let fragment_push = fragment_res.push_constant_buffers.first();
self.reflect_push_constant_buffer(vertex_push, fragment_push)?;
Ok(())
}
}
#[cfg(test)]
@ -753,59 +827,53 @@ mod test {
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::front::{Glslang, ShaderInputCompiler};
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use librashader_common::map::FastHashMap;
use librashader_common::map::{FastHashMap, ShortString};
use librashader_preprocess::ShaderSource;
use spirv_cross::glsl::{CompilerOptions, Version};
use spirv_cross::hlsl::ShaderModel;
use spirv_cross::{glsl, hlsl};
#[test]
pub fn test_into() {
let result = ShaderSource::load(&librashader_common::ShaderStorage::Path(
"../test/basic.slang".into(),
))
.unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
param.1.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
let spirv = Glslang::compile(&result).unwrap();
let mut reflect = CrossReflect::<hlsl::Target>::try_from(&spirv).unwrap();
let shader_reflection = reflect
.reflect(
0,
&ShaderSemantics {
uniform_semantics,
texture_semantics: Default::default(),
},
)
.unwrap();
let mut opts = hlsl::CompilerOptions::default();
opts.shader_model = ShaderModel::V3_0;
let compiled: ShaderCompilerOutput<String, CrossHlslContext> =
<CrossReflect<hlsl::Target> as CompileShader<HLSL>>::compile(
reflect,
Some(ShaderModel::V3_0),
)
.unwrap();
println!("{:?}", shader_reflection.meta);
println!("{}", compiled.fragment);
println!("{}", compiled.vertex);
// // eprintln!("{shader_reflection:#?}");
// eprintln!("{}", compiled.fragment)
// let mut loader = rspirv::dr::Loader::new();
// rspirv::binary::parse_words(spirv.fragment.as_binary(), &mut loader).unwrap();
// let module = loader.module();
// println!("{:#}", module.disassemble());
}
// #[test]
// pub fn test_into() {
// let result = ShaderSource::load("../test/basic.slang").unwrap();
// let mut uniform_semantics: FastHashMap<ShortString, UniformSemantic> = Default::default();
//
// for (_index, param) in result.parameters.iter().enumerate() {
// uniform_semantics.insert(
// param.1.id.clone(),
// UniformSemantic::Unique(Semantic {
// semantics: UniqueSemantics::FloatParameter,
// index: (),
// }),
// );
// }
// let spirv = Glslang::compile(&result).unwrap();
// let mut reflect = CrossReflect::<hlsl::Target>::try_from(&spirv).unwrap();
// let shader_reflection = reflect
// .reflect(
// 0,
// &ShaderSemantics {
// uniform_semantics,
// texture_semantics: Default::default(),
// },
// )
// .unwrap();
// let mut opts = hlsl::CompilerOptions::default();
// opts.shader_model = ShaderModel::V3_0;
//
// let compiled: ShaderCompilerOutput<String, CrossHlslContext> =
// <CrossReflect<hlsl::Target> as CompileShader<HLSL>>::compile(
// reflect,
// Some(ShaderModel::V3_0),
// )
// .unwrap();
//
// println!("{:?}", shader_reflection.meta);
// println!("{}", compiled.fragment);
// println!("{}", compiled.vertex);
//
// // // eprintln!("{shader_reflection:#?}");
// // eprintln!("{}", compiled.fragment)
// // let mut loader = rspirv::dr::Loader::new();
// // rspirv::binary::parse_words(spirv.fragment.as_binary(), &mut loader).unwrap();
// // let module = loader.module();
// // println!("{:#}", module.disassemble());
// }
}

View file

@ -2,103 +2,112 @@ use crate::back::msl::CrossMslContext;
use crate::back::targets::MSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
use spirv_cross::msl;
use spirv_cross::msl::{ResourceBinding, ResourceBindingLocation};
use spirv_cross::spirv::{Ast, Decoration, ExecutionModel};
use std::collections::BTreeMap;
use crate::reflect::cross::{CompiledProgram, CrossReflect};
pub(crate) type MslReflect = CrossReflect<spirv_cross::msl::Target>;
use spirv::Decoration;
use spirv_cross2::compile::msl::{BindTarget, ResourceBinding};
use spirv_cross2::compile::{msl, CompilableTarget};
use spirv_cross2::reflect::{DecorationValue, ResourceType};
use spirv_cross2::{targets, Compiler};
impl CompileShader<MSL> for CrossReflect<spirv_cross::msl::Target> {
type Options = Option<spirv_cross::msl::Version>;
pub(crate) type MslReflect = CrossReflect<targets::Msl>;
impl CompileShader<MSL> for CrossReflect<targets::Msl> {
type Options = Option<msl::MslVersion>;
type Context = CrossMslContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, CrossMslContext>, ShaderCompileError> {
let version = options.unwrap_or(msl::Version::V2_0);
let mut vert_options = spirv_cross::msl::CompilerOptions::default();
let mut frag_options = spirv_cross::msl::CompilerOptions::default();
vert_options.version = version;
frag_options.version = version;
let version = options.unwrap_or(msl::MslVersion::new(2, 0, 0));
let mut options = targets::Msl::options();
options.version = version;
fn set_bindings(
ast: &Ast<msl::Target>,
stage: ExecutionModel,
binding_map: &mut BTreeMap<ResourceBindingLocation, ResourceBinding>,
ast: &mut Compiler<targets::Msl>,
stage: spirv::ExecutionModel,
) -> Result<(), ShaderCompileError> {
let resources = ast.get_shader_resources()?;
for resource in &resources.push_constant_buffers {
let location = ResourceBindingLocation {
stage,
desc_set: msl::PUSH_CONSTANT_DESCRIPTOR_SET,
binding: msl::PUSH_CONSTANT_BINDING,
};
let overridden = ResourceBinding {
buffer_id: ast.get_decoration(resource.id, Decoration::Binding)?,
texture_id: 0,
sampler_id: 0,
base_type: None,
count: 0, // no arrays allowed in slang shaders, otherwise we'd have to get the type and get the array length
let resources = ast.shader_resources()?;
for resource in resources.resources_for_type(ResourceType::PushConstant)? {
let Some(DecorationValue::Literal(buffer)) =
ast.decoration(resource.id, Decoration::Binding)?
else {
continue;
};
binding_map.insert(location, overridden);
ast.add_resource_binding(
stage,
ResourceBinding::PushConstantBuffer,
&BindTarget {
buffer,
texture: 0,
sampler: 0,
count: None,
},
)?
}
for resource in resources
.uniform_buffers
.iter()
.chain(resources.sampled_images.iter())
{
let binding = ast.get_decoration(resource.id, Decoration::Binding)?;
let location = ResourceBindingLocation {
let ubos = resources.resources_for_type(ResourceType::UniformBuffer)?;
let sampled = resources.resources_for_type(ResourceType::SampledImage)?;
for resource in ubos.chain(sampled) {
let Some(DecorationValue::Literal(binding)) =
ast.decoration(resource.id, Decoration::Binding)?
else {
continue;
};
let Some(DecorationValue::Literal(desc_set)) =
ast.decoration(resource.id, Decoration::DescriptorSet)?
else {
continue;
};
let overridden = BindTarget {
buffer: binding,
texture: binding,
sampler: binding,
count: None,
};
ast.add_resource_binding(
stage,
desc_set: ast.get_decoration(resource.id, Decoration::DescriptorSet)?,
binding,
};
let overridden = ResourceBinding {
buffer_id: binding,
texture_id: binding,
sampler_id: binding,
base_type: None,
count: 0, // no arrays allowed in slang shaders, otherwise we'd have to get the type and get the array length
};
binding_map.insert(location, overridden);
ResourceBinding::Qualified {
set: desc_set,
binding,
},
&overridden,
)?
}
Ok(())
}
set_bindings(
&self.vertex,
ExecutionModel::Vertex,
&mut vert_options.resource_binding_overrides,
)?;
set_bindings(&mut self.vertex, spirv::ExecutionModel::Vertex)?;
set_bindings(
&self.fragment,
ExecutionModel::Fragment,
&mut frag_options.resource_binding_overrides,
)?;
set_bindings(&mut self.fragment, spirv::ExecutionModel::Fragment)?;
self.vertex.set_compiler_options(&vert_options)?;
self.fragment.set_compiler_options(&frag_options)?;
let vertex_compiled = self.vertex.compile(&options)?;
let fragment_compiled = self.fragment.compile(&options)?;
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
vertex: vertex_compiled.to_string(),
fragment: fragment_compiled.to_string(),
context: CrossMslContext {
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
vertex: vertex_compiled,
fragment: fragment_compiled,
},
},
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
<CrossReflect<targets::Msl> as CompileShader<MSL>>::compile(*self, options)
}
}
#[cfg(test)]
@ -106,26 +115,21 @@ mod test {
use crate::back::targets::{MSL, WGSL};
use crate::back::{CompileShader, FromCompilation};
use crate::reflect::cross::SpirvCross;
use crate::reflect::naga::{Naga, NagaLoweringOptions};
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use crate::reflect::ReflectShader;
use bitflags::Flags;
use librashader_common::map::FastHashMap;
use librashader_common::map::{FastHashMap, ShortString};
use librashader_preprocess::ShaderSource;
use rustc_hash::FxHashMap;
use spirv_cross::msl;
use std::io::Write;
use spirv_cross2::compile::msl::MslVersion;
#[test]
pub fn test_into() {
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
let result = ShaderSource::load(&librashader_common::ShaderStorage::Path(
"../test/basic.slang".into(),
))
.unwrap();
let result = ShaderSource::load("../test/basic.slang").unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
let mut uniform_semantics: FastHashMap<ShortString, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
@ -151,7 +155,7 @@ mod test {
)
.expect("");
let compiled = msl.compile(Some(msl::Version::V2_0)).unwrap();
let compiled = msl.compile(Some(MslVersion::new(2, 0, 0))).unwrap();
println!("{}", compiled.vertex);
}

View file

@ -25,6 +25,9 @@ pub trait ReflectShader {
pass_number: usize,
semantics: &ShaderSemantics,
) -> Result<ShaderReflection, ShaderReflectError>;
/// Validate the shader without doing reflection against a set of semantics.
fn validate(&mut self) -> Result<(), ShaderReflectError>;
}
pub use semantics::ShaderReflection;

View file

@ -10,16 +10,8 @@ pub mod wgsl;
use crate::error::{SemanticsErrorKind, ShaderReflectError};
use std::fmt::Debug;
use crate::front::SpirvCompilation;
use naga::{
AddressSpace, Binding, Expression, GlobalVariable, Handle, ImageClass, Module, ResourceBinding,
Scalar, ScalarKind, StructMember, TypeInner, VectorSize,
};
use rspirv::binary::Assemble;
use rspirv::dr::Builder;
use rustc_hash::FxHashSet;
use crate::front::spirv_passes::lower_samplers;
use crate::front::SpirvCompilation;
use crate::reflect::helper::{SemanticErrorBlame, TextureData, UboData};
use crate::reflect::semantics::{
BindingMeta, BindingStage, BufferReflection, MemberOffset, ShaderSemantics, TextureBinding,
@ -28,6 +20,14 @@ use crate::reflect::semantics::{
MAX_PUSH_BUFFER_SIZE,
};
use crate::reflect::{align_uniform_size, ReflectShader, ShaderReflection};
use librashader_common::map::ShortString;
use naga::{
AddressSpace, Binding, Expression, GlobalVariable, Handle, ImageClass, Module, ResourceBinding,
Scalar, ScalarKind, StructMember, TypeInner, VectorSize,
};
use rspirv::binary::Assemble;
use rspirv::dr::Builder;
use rustc_hash::FxHashSet;
/// Reflect under Naga semantics
///
@ -212,7 +212,7 @@ impl ValidateTypeSemantics<&TypeInner> for UniqueSemantics {
}
};
return None;
None
}
}
@ -239,7 +239,7 @@ impl ValidateTypeSemantics<&TypeInner> for TextureSemantics {
impl NagaReflect {
fn reflect_ubos(
&mut self,
&self,
vertex_ubo: Option<Handle<GlobalVariable>>,
fragment_ubo: Option<Handle<GlobalVariable>>,
) -> Result<Option<BufferReflection<u32>>, ShaderReflectError> {
@ -429,7 +429,7 @@ impl NagaReflect {
}
}
fn validate(&self) -> Result<(), ShaderReflectError> {
fn validate_semantics(&self) -> Result<(), ShaderReflectError> {
// Verify types
if self.vertex.global_variables.iter().any(|(_, gv)| {
let ty = &self.vertex.types[gv.ty];
@ -566,7 +566,7 @@ impl NagaReflect {
};
let &Some(Binding::Location { location, .. }) = &frag_output.binding else {
return Err(ShaderReflectError::VertexSemanticError(
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
@ -670,7 +670,7 @@ impl NagaReflect {
let member_type = &module.types[member.ty].inner;
if let Some(parameter) = semantics.uniform_semantics.get_unique_semantic(&name) {
if let Some(parameter) = semantics.uniform_semantics.unique_semantic(&name) {
let Some(typeinfo) = parameter.semantics.validate_type(&member_type) else {
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)));
};
@ -678,9 +678,11 @@ impl NagaReflect {
match &parameter.semantics {
UniqueSemantics::FloatParameter => {
let offset = member.offset;
if let Some(meta) = meta.parameter_meta.get_mut(&name) {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset as usize
if let Some(meta) = meta.parameter_meta.get_mut::<str>(name.as_ref()) {
if let Some(expected) = meta
.offset
.offset(offset_type)
.filter(|expected| *expected != offset as usize)
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
@ -701,6 +703,7 @@ impl NagaReflect {
*meta.offset.offset_mut(offset_type) = Some(offset as usize);
} else {
let name = ShortString::from(name);
meta.parameter_meta.insert(
name.clone(),
VariableMeta {
@ -714,8 +717,10 @@ impl NagaReflect {
semantics => {
let offset = member.offset;
if let Some(meta) = meta.unique_meta.get_mut(semantics) {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset as usize
if let Some(expected) = meta
.offset
.offset(offset_type)
.filter(|expected| *expected != offset as usize)
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
@ -739,7 +744,7 @@ impl NagaReflect {
meta.unique_meta.insert(
*semantics,
VariableMeta {
id: name,
id: ShortString::from(name),
offset: MemberOffset::new(offset as usize, offset_type),
size: typeinfo.size * typeinfo.columns,
},
@ -747,7 +752,7 @@ impl NagaReflect {
}
}
}
} else if let Some(texture) = semantics.uniform_semantics.get_texture_semantic(&name) {
} else if let Some(texture) = semantics.uniform_semantics.texture_semantic(&name) {
let Some(_typeinfo) = texture.semantics.validate_type(&member_type) else {
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)));
};
@ -763,8 +768,10 @@ impl NagaReflect {
let offset = member.offset;
if let Some(meta) = meta.texture_size_meta.get_mut(&texture) {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset as usize
if let Some(expected) = meta
.offset
.offset(offset_type)
.filter(|expected| *expected != offset as usize)
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
@ -790,7 +797,7 @@ impl NagaReflect {
SemanticErrorBlame::Vertex => BindingStage::VERTEX,
SemanticErrorBlame::Fragment => BindingStage::FRAGMENT,
},
id: name,
id: ShortString::from(name),
},
);
}
@ -844,10 +851,7 @@ impl NagaReflect {
semantics: &ShaderSemantics,
meta: &mut BindingMeta,
) -> Result<(), ShaderReflectError> {
let Some(semantic) = semantics
.texture_semantics
.get_texture_semantic(texture.name)
else {
let Some(semantic) = semantics.texture_semantics.texture_semantic(texture.name) else {
return Err(
SemanticErrorBlame::Fragment.error(SemanticsErrorKind::UnknownSemantics(
texture.name.to_string(),
@ -878,7 +882,7 @@ impl ReflectShader for NagaReflect {
pass_number: usize,
semantics: &ShaderSemantics,
) -> Result<ShaderReflection, ShaderReflectError> {
self.validate()?;
self.validate_semantics()?;
// Validate verifies that there's only one uniform block.
let vertex_ubo = self
@ -1008,10 +1012,41 @@ impl ReflectShader for NagaReflect {
meta,
})
}
fn validate(&mut self) -> Result<(), ShaderReflectError> {
self.validate_semantics()?;
let vertex_push = self
.vertex
.global_variables
.iter()
.find_map(|(handle, gv)| {
if gv.space == AddressSpace::PushConstant {
Some(handle)
} else {
None
}
});
let fragment_push = self
.fragment
.global_variables
.iter()
.find_map(|(handle, gv)| {
if gv.space == AddressSpace::PushConstant {
Some(handle)
} else {
None
}
});
self.reflect_push_constant_buffer(vertex_push, fragment_push)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::front::ShaderInputCompiler;
use crate::reflect::semantics::{Semantic, TextureSemantics, UniformSemantic};
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
@ -1019,18 +1054,19 @@ mod test {
// #[test]
// pub fn test_into() {
// let result = ShaderSource::load("../test/slang-shaders/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let compilation = crate::front::GlslangCompilation::try_from(&result).unwrap();
// let result = ShaderSource::load("../test/slang-shaders/misc/shaders/simple_color_controls.slang").unwrap();
// let compilation = crate::front::Glslang::compile(&result).unwrap();
//
// let mut loader = rspirv::dr::Loader::new();
// rspirv::binary::parse_words(compilation.vertex.as_binary(), &mut loader).unwrap();
// let module = loader.module();
//
// let outputs: Vec<&Instruction> = module
// .types_global_values
// .iter()
// .filter(|i| i.class.opcode == Op::Variable)
// .collect();
// crate::front::n
// // let mut loader = rspirv::dr::Loader::new();
// // rspirv::binary::parse_words(compilation.vertex.as_binary(), &mut loader).unwrap();
// // let module = loader.module();
// //
// // let outputs: Vec<&Instruction> = module
// // .types_global_values
// // .iter()
// // .filter(|i| i.class.opcode == Op::Variable)
// // .collect();
//
// println!("{outputs:#?}");
// }

View file

@ -8,19 +8,9 @@ use naga::back::msl::{
};
use naga::valid::{Capabilities, ValidationFlags};
use naga::{Module, TypeInner};
use spirv_cross::msl::Version;
fn msl_version_to_naga_msl(version: MslVersion) -> (u8, u8) {
match version {
Version::V1_0 => (1, 0),
Version::V1_1 => (1, 1),
Version::V1_2 => (1, 2),
Version::V2_0 => (2, 0),
Version::V2_1 => (2, 1),
Version::V2_2 => (2, 2),
Version::V2_3 => (2, 3),
_ => (0, 0),
}
(version.major as u8, version.minor as u8)
}
impl CompileShader<MSL> for NagaReflect {
@ -33,7 +23,7 @@ impl CompileShader<MSL> for NagaReflect {
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
// https://github.com/libretro/RetroArch/blob/434e94c782af2e4d4277a24b7ed8e5fc54870088/gfx/drivers_shader/slang_process.cpp#L524
let lang_version = msl_version_to_naga_msl(options.unwrap_or(MslVersion::V2_0));
let lang_version = msl_version_to_naga_msl(options.unwrap_or(MslVersion::new(2, 0, 0)));
let mut vert_options = Options {
lang_version,
@ -156,6 +146,13 @@ impl CompileShader<MSL> for NagaReflect {
},
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
<NagaReflect as CompileShader<MSL>>::compile(*self, options)
}
}
#[cfg(test)]
@ -166,47 +163,44 @@ mod test {
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use crate::reflect::ReflectShader;
use bitflags::Flags;
use librashader_common::map::FastHashMap;
use librashader_common::map::{FastHashMap, ShortString};
use librashader_preprocess::ShaderSource;
use spirv_cross::msl;
// use spirv_cross::msl;
#[test]
pub fn test_into() {
let result = ShaderSource::load(&librashader_common::ShaderStorage::Path(
"../test/basic.slang".into(),
))
.unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
param.1.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
let compilation = crate::front::SpirvCompilation::try_from(&result).unwrap();
let mut msl = <MSL as FromCompilation<_, Naga>>::from_compilation(compilation).unwrap();
msl.reflect(
0,
&ShaderSemantics {
uniform_semantics,
texture_semantics: Default::default(),
},
)
.expect("");
let compiled = msl.compile(Some(msl::Version::V2_0)).unwrap();
println!(
"{:?}",
compiled.context.fragment.translation_info.entry_point_names
);
}
// #[test]
// pub fn test_into() {
// let result = ShaderSource::load("../test/basic.slang").unwrap();
//
// let mut uniform_semantics: FastHashMap<ShortString, UniformSemantic> = Default::default();
//
// for (_index, param) in result.parameters.iter().enumerate() {
// uniform_semantics.insert(
// param.1.id.clone(),
// UniformSemantic::Unique(Semantic {
// semantics: UniqueSemantics::FloatParameter,
// index: (),
// }),
// );
// }
//
// let compilation = crate::front::SpirvCompilation::try_from(&result).unwrap();
//
// let mut msl = <MSL as FromCompilation<_, Naga>>::from_compilation(compilation).unwrap();
//
// msl.reflect(
// 0,
// &ShaderSemantics {
// uniform_semantics,
// texture_semantics: Default::default(),
// },
// )
// .expect("");
//
// let compiled = msl.compile(Some(msl::Version::V2_0)).unwrap();
//
// println!(
// "{:?}",
// compiled.context.fragment.translation_info.entry_point_names
// );
// }
}

View file

@ -51,4 +51,11 @@ impl CompileShader<SPIRV> for NagaReflect {
},
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<Vec<u32>, Self::Context>, ShaderCompileError> {
<NagaReflect as CompileShader<SPIRV>>::compile(*self, options)
}
}

View file

@ -38,4 +38,11 @@ impl CompileShader<WGSL> for NagaReflect {
},
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
<NagaReflect as CompileShader<WGSL>>::compile(*self, options)
}
}

View file

@ -5,9 +5,10 @@ use crate::front::{ShaderInputCompiler, ShaderReflectObject};
use crate::reflect::semantics::{
Semantic, ShaderSemantics, TextureSemantics, UniformSemantic, UniqueSemantics,
};
use librashader_common::map::FastHashMap;
use librashader_common::map::{FastHashMap, ShortString};
use librashader_pack::PassResource;
use librashader_preprocess::{PreprocessError, ShaderSource};
use librashader_presets::{ShaderPassConfig, TextureConfig};
use librashader_presets::{ShaderPreset, TextureMeta};
/// Artifacts of a reflected and compiled shader pass.
///
@ -27,7 +28,7 @@ use librashader_presets::{ShaderPassConfig, TextureConfig};
/// ```
///
/// This allows a runtime to not name the backing type of the compiled artifact if not necessary.
pub type ShaderPassArtifact<T> = (ShaderPassConfig, ShaderSource, CompilerBackend<T>);
pub type ShaderPassArtifact<T> = (PassResource, CompilerBackend<T>);
impl<T: OutputTarget> CompilePresetTarget for T {}
@ -36,9 +37,9 @@ impl<T: OutputTarget> CompilePresetTarget for T {}
pub trait CompilePresetTarget: OutputTarget {
/// Compile passes of a shader preset given the applicable
/// shader output target, compilation type, and resulting error.
fn compile_preset_passes<I, R, E>(
passes: Vec<ShaderPassConfig>,
textures: &[TextureConfig],
fn compile_preset_passes<'a, I, R, E>(
passes: impl IntoIterator<Item = PassResource>,
textures: impl Iterator<Item = &'a TextureMeta>,
) -> Result<
(
Vec<ShaderPassArtifact<<Self as FromCompilation<I, R>>::Output>>,
@ -61,9 +62,9 @@ pub trait CompilePresetTarget: OutputTarget {
/// Compile passes of a shader preset given the applicable
/// shader output target, compilation type, and resulting error.
fn compile_preset_passes<T, I, R, E>(
passes: Vec<ShaderPassConfig>,
textures: &[TextureConfig],
fn compile_preset_passes<'a, T, I, R, E>(
passes: impl IntoIterator<Item = PassResource>,
textures: impl Iterator<Item = &'a TextureMeta>,
) -> Result<
(
Vec<ShaderPassArtifact<<T as FromCompilation<I, R>>::Output>>,
@ -80,15 +81,15 @@ where
E: From<ShaderReflectError>,
E: From<ShaderCompileError>,
{
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
let mut texture_semantics: FastHashMap<String, Semantic<TextureSemantics>> = Default::default();
let mut uniform_semantics: FastHashMap<ShortString, UniformSemantic> = Default::default();
let mut texture_semantics: FastHashMap<ShortString, Semantic<TextureSemantics>> =
Default::default();
let passes = passes
let artifacts = passes
.into_iter()
.map(|shader| {
let source: ShaderSource = ShaderSource::load(&shader.name)?;
let compiled = I::Compiler::compile(&source)?;
let source = &shader.data;
let compiled = I::Compiler::compile(source)?;
let reflect = T::from_compilation(compiled)?;
for parameter in source.parameters.values() {
@ -100,13 +101,25 @@ where
}),
);
}
Ok::<_, E>((shader, source, reflect))
Ok::<_, E>((shader, reflect))
})
.collect::<Result<Vec<(ShaderPassConfig, ShaderSource, CompilerBackend<_>)>, E>>()?;
.collect::<Result<Vec<(PassResource, CompilerBackend<_>)>, E>>()?;
for details in &passes {
insert_pass_semantics(&mut uniform_semantics, &mut texture_semantics, &details.0)
for (pass, _) in artifacts.iter() {
insert_pass_semantics(
&mut uniform_semantics,
&mut texture_semantics,
pass.meta.alias.as_ref(),
pass.meta.id as usize,
);
insert_pass_semantics(
&mut uniform_semantics,
&mut texture_semantics,
pass.data.name.as_ref(),
pass.meta.id as usize,
);
}
insert_lut_semantics(textures, &mut uniform_semantics, &mut texture_semantics);
let semantics = ShaderSemantics {
@ -114,16 +127,17 @@ where
texture_semantics,
};
Ok((passes, semantics))
Ok((artifacts, semantics))
}
/// Insert the available semantics for the input pass config into the provided semantic maps.
fn insert_pass_semantics(
uniform_semantics: &mut FastHashMap<String, UniformSemantic>,
texture_semantics: &mut FastHashMap<String, Semantic<TextureSemantics>>,
config: &ShaderPassConfig,
uniform_semantics: &mut FastHashMap<ShortString, UniformSemantic>,
texture_semantics: &mut FastHashMap<ShortString, Semantic<TextureSemantics>>,
alias: Option<&ShortString>,
index: usize,
) {
let Some(alias) = &config.alias else {
let Some(alias) = alias else {
return;
};
@ -132,8 +146,6 @@ fn insert_pass_semantics(
return;
}
let index = config.id as usize;
// PassOutput
texture_semantics.insert(
alias.clone(),
@ -142,24 +154,32 @@ fn insert_pass_semantics(
index,
},
);
let mut alias_size = alias.clone();
alias_size.push_str("Size");
uniform_semantics.insert(
format!("{alias}Size"),
alias_size,
UniformSemantic::Texture(Semantic {
semantics: TextureSemantics::PassOutput,
index,
}),
);
let mut alias_feedback = alias.clone();
alias_feedback.push_str("Feedback");
// PassFeedback
texture_semantics.insert(
format!("{alias}Feedback"),
alias_feedback,
Semantic {
semantics: TextureSemantics::PassFeedback,
index,
},
);
let mut alias_feedback_size = alias.clone();
alias_feedback_size.push_str("FeedbackSize");
uniform_semantics.insert(
format!("{alias}FeedbackSize"),
alias_feedback_size,
UniformSemantic::Texture(Semantic {
semantics: TextureSemantics::PassFeedback,
index,
@ -168,12 +188,15 @@ fn insert_pass_semantics(
}
/// Insert the available semantics for the input texture config into the provided semantic maps.
fn insert_lut_semantics(
textures: &[TextureConfig],
uniform_semantics: &mut FastHashMap<String, UniformSemantic>,
texture_semantics: &mut FastHashMap<String, Semantic<TextureSemantics>>,
fn insert_lut_semantics<'a>(
textures: impl Iterator<Item = &'a TextureMeta>,
uniform_semantics: &mut FastHashMap<ShortString, UniformSemantic>,
texture_semantics: &mut FastHashMap<ShortString, Semantic<TextureSemantics>>,
) {
for (index, texture) in textures.iter().enumerate() {
for (index, texture) in textures.enumerate() {
let mut size_semantic = texture.name.clone();
size_semantic.push_str("Size");
texture_semantics.insert(
texture.name.clone(),
Semantic {
@ -183,7 +206,7 @@ fn insert_lut_semantics(
);
uniform_semantics.insert(
format!("{}Size", texture.name),
size_semantic,
UniformSemantic::Texture(Semantic {
semantics: TextureSemantics::User,
index,
@ -191,3 +214,61 @@ fn insert_lut_semantics(
);
}
}
impl ShaderSemantics {
/// Create pass semantics for a single pass in the given shader preset.
///
/// This is meant as a convenience function for reflection use only.
pub fn create_pass_semantics<E>(
preset: &ShaderPreset,
index: usize,
) -> Result<ShaderSemantics, E>
where
E: From<ShaderReflectError>,
E: From<PreprocessError>,
{
let mut uniform_semantics: FastHashMap<ShortString, UniformSemantic> = Default::default();
let mut texture_semantics: FastHashMap<ShortString, Semantic<TextureSemantics>> =
Default::default();
let config = preset
.passes
.get(index)
.ok_or_else(|| PreprocessError::InvalidStage)?;
let source = ShaderSource::load(&config.storage)?;
for parameter in source.parameters.values() {
uniform_semantics.insert(
parameter.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
insert_pass_semantics(
&mut uniform_semantics,
&mut texture_semantics,
config.meta.alias.as_ref(),
config.meta.id as usize,
);
insert_pass_semantics(
&mut uniform_semantics,
&mut texture_semantics,
source.name.as_ref(),
config.meta.id as usize,
);
insert_lut_semantics(
preset.textures.iter().map(|t| &t.meta),
&mut uniform_semantics,
&mut texture_semantics,
);
Ok(ShaderSemantics {
uniform_semantics,
texture_semantics,
})
}
}

View file

@ -1,5 +1,6 @@
use bitflags::bitflags;
use librashader_common::map::FastHashMap;
use librashader_common::map::{FastHashMap, ShortString};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
/// The maximum number of bindings allowed in a shader.
@ -9,6 +10,7 @@ pub const MAX_PUSH_BUFFER_SIZE: u32 = 128;
/// The type of a uniform.
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UniformType {
/// A matrix of 4x4 floats (`mat4`).
Mat4,
@ -26,6 +28,7 @@ pub enum UniformType {
/// that are always available.
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash)]
#[repr(i32)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UniqueSemantics {
// mat4, MVP
/// The Model View Projection matrix for the frame.
@ -77,6 +80,27 @@ impl UniqueSemantics {
UniqueSemantics::FloatParameter => UniformType::Float,
}
}
/// Get the name of the semantic as a string.
pub const fn as_str(&self) -> &'static str {
match self {
UniqueSemantics::MVP => "MVP",
UniqueSemantics::Output => "Output",
UniqueSemantics::FinalViewport => "FinalViewport",
UniqueSemantics::FrameCount => "FrameCount",
UniqueSemantics::FrameDirection => "FrameDirection",
UniqueSemantics::Rotation => "Rotation",
UniqueSemantics::TotalSubFrames => "TotalSubFrames",
UniqueSemantics::CurrentSubFrame => "CurrentSubFrame",
UniqueSemantics::FloatParameter => "FloatParameter",
}
}
}
impl Display for UniqueSemantics {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
/// Texture semantics relate to input or output textures.
@ -84,6 +108,7 @@ impl UniqueSemantics {
/// Texture semantics are used to relate both texture samplers and `*Size` uniforms.
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash)]
#[repr(i32)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TextureSemantics {
/// The original input of the filter chain.
Original = 0,
@ -173,6 +198,8 @@ pub struct Semantic<T, I = usize> {
bitflags! {
/// The pipeline stage for which a uniform is bound.
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct BindingStage: u8 {
const NONE = 0b00000000;
const VERTEX = 0b00000001;
@ -181,7 +208,8 @@ bitflags! {
}
/// Reflection information for the Uniform Buffer or Push Constant Block
#[derive(Debug)]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BufferReflection<T> {
/// The binding point for this buffer, if applicable
pub binding: T,
@ -195,6 +223,7 @@ pub struct BufferReflection<T> {
///
/// A uniform can be bound to both the UBO, or as a Push Constant.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MemberOffset {
/// The offset of the uniform member within the UBO.
pub ubo: Option<usize>,
@ -203,6 +232,7 @@ pub struct MemberOffset {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// The block where a uniform member is located.
pub enum UniformMemberBlock {
/// The offset is for a UBO.
@ -247,19 +277,20 @@ impl MemberOffset {
}
/// Reflection information about a non-texture related uniform variable.
#[derive(Debug)]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VariableMeta {
// this might bite us in the back because retroarch keeps separate UBO/push offsets.. eh
/// The offset of this variable uniform.
pub offset: MemberOffset,
/// The size of the uniform.
pub size: u32,
/// The name of the uniform.
pub id: String,
pub id: ShortString,
}
/// Reflection information about a texture size uniform variable.
#[derive(Debug)]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TextureSizeMeta {
// this might bite us in the back because retroarch keeps separate UBO/push offsets..
/// The offset of this size uniform.
@ -267,18 +298,20 @@ pub struct TextureSizeMeta {
/// The mask indicating for which stages the texture size uniform should be bound.
pub stage_mask: BindingStage,
/// The name of the uniform.
pub id: String,
pub id: ShortString,
}
/// Reflection information about texture samplers.
#[derive(Debug)]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TextureBinding {
/// The binding index of the texture.
pub binding: u32,
}
/// Reflection information about a shader.
#[derive(Debug)]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShaderReflection {
/// Reflection information about the UBO for this shader.
pub ubo: Option<BufferReflection<u32>>,
@ -318,11 +351,11 @@ impl UniformMeta for TextureSizeMeta {
/// A trait for maps that can return texture semantic units.
pub trait TextureSemanticMap {
/// Get the texture semantic for the given variable name.
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>>;
fn texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>>;
}
impl TextureSemanticMap for FastHashMap<String, UniformSemantic> {
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
impl TextureSemanticMap for FastHashMap<ShortString, UniformSemantic> {
fn texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
match self.get(name) {
None => {
if let Some(semantics) = TextureSemantics::TEXTURE_SEMANTICS
@ -353,8 +386,8 @@ impl TextureSemanticMap for FastHashMap<String, UniformSemantic> {
}
}
impl TextureSemanticMap for FastHashMap<String, Semantic<TextureSemantics>> {
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
impl TextureSemanticMap for FastHashMap<ShortString, Semantic<TextureSemantics>> {
fn texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
match self.get(name) {
None => {
if let Some(semantics) = TextureSemantics::TEXTURE_SEMANTICS
@ -387,11 +420,11 @@ impl TextureSemanticMap for FastHashMap<String, Semantic<TextureSemantics>> {
/// A trait for maps that can return unique semantic units.
pub trait UniqueSemanticMap {
/// Get the unique semantic for the given variable name.
fn get_unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>>;
fn unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>>;
}
impl UniqueSemanticMap for FastHashMap<String, UniformSemantic> {
fn get_unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>> {
impl UniqueSemanticMap for FastHashMap<ShortString, UniformSemantic> {
fn unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>> {
match self.get(name) {
// existing uniforms in the semantic map have priority
None => match name {
@ -424,7 +457,7 @@ impl UniqueSemanticMap for FastHashMap<String, UniformSemantic> {
index: (),
}),
"CurrentSubFrame" => Some(Semantic {
semantics: UniqueSemantics::TotalSubFrames,
semantics: UniqueSemantics::CurrentSubFrame,
index: (),
}),
_ => None,
@ -437,6 +470,7 @@ impl UniqueSemanticMap for FastHashMap<String, UniformSemantic> {
/// Semantic assignment of a shader uniform to filter chain semantics.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UniformSemantic {
/// A unique semantic.
Unique(Semantic<UniqueSemantics, ()>),
@ -446,21 +480,23 @@ pub enum UniformSemantic {
/// The runtime provided maps of uniform and texture variables to filter chain semantics.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShaderSemantics {
/// A map of uniform names to filter chain semantics.
pub uniform_semantics: FastHashMap<String, UniformSemantic>,
pub uniform_semantics: FastHashMap<ShortString, UniformSemantic>,
/// A map of texture names to filter chain semantics.
pub texture_semantics: FastHashMap<String, Semantic<TextureSemantics>>,
pub texture_semantics: FastHashMap<ShortString, Semantic<TextureSemantics>>,
}
/// The binding of a uniform after the shader has been linked.
///
/// Used in combination with [`MemberOffset`](crate::reflect::semantics::MemberOffset) to keep track
/// Used in combination with [`MemberOffset`] to keep track
/// of semantics at each frame pass.
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UniformBinding {
/// A user parameter (`float`) binding.
Parameter(String),
Parameter(ShortString),
/// A known semantic binding.
SemanticVariable(UniqueSemantics),
/// A texture size (`float4`) binding.
@ -480,14 +516,162 @@ impl From<Semantic<TextureSemantics>> for UniformBinding {
}
/// Reflection metadata about the various bindings for this shader.
#[derive(Debug, Default)]
#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BindingMeta {
#[cfg_attr(feature = "serde", serde(rename = "param"))]
/// A map of parameter names to uniform binding metadata.
pub parameter_meta: FastHashMap<String, VariableMeta>,
pub parameter_meta: FastHashMap<ShortString, VariableMeta>,
#[cfg_attr(feature = "serde", serde(rename = "unique"))]
/// A map of unique semantics to uniform binding metadata.
pub unique_meta: FastHashMap<UniqueSemantics, VariableMeta>,
#[cfg_attr(feature = "serde", serde(rename = "texture"))]
/// A map of texture semantics to texture binding points.
pub texture_meta: FastHashMap<Semantic<TextureSemantics>, TextureBinding>,
#[cfg_attr(feature = "serde", serde(rename = "texture_size"))]
/// A map of texture semantics to texture size uniform binding metadata.
pub texture_size_meta: FastHashMap<Semantic<TextureSemantics>, TextureSizeMeta>,
}
#[cfg(feature = "serde")]
mod serde_impl {
use super::*;
use serde::de::{Deserialize, Visitor};
use serde::ser::Serialize;
use serde::{Deserializer, Serializer};
struct TextureSemanticVisitor;
impl<'de> Visitor<'de> for TextureSemanticVisitor {
type Value = Semantic<TextureSemantics>;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a string of the form (Semantic)N?")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match v {
"Original" => Ok(TextureSemantics::Original.semantics(0)),
"Source" => Ok(TextureSemantics::Source.semantics(0)),
other => {
let Some(index) = other.find(|c: char| c.is_digit(10)) else {
return Err(E::custom(format!(
"expected index for indexed texture semantic {v}"
)));
};
let (semantic, index) = other.split_at(index);
let Ok(index) = index.parse::<usize>() else {
return Err(E::custom(format!(
"could not parse index {index} of texture semantic {v}"
)));
};
match semantic {
"OriginalHistory" => Ok(TextureSemantics::OriginalHistory.semantics(index)),
"PassOutput" => Ok(TextureSemantics::PassOutput.semantics(index)),
"PassFeedback" => Ok(TextureSemantics::PassFeedback.semantics(index)),
// everything else (including "User") is a user semantic.
_ => Ok(TextureSemantics::User.semantics(index)),
}
}
}
}
}
impl<'de> Deserialize<'de> for Semantic<TextureSemantics> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(TextureSemanticVisitor)
}
}
impl Serialize for Semantic<TextureSemantics> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if self.semantics.is_indexed() {
serializer.serialize_str(&format!(
"{}{}",
self.semantics.texture_name(),
self.index
))
} else {
serializer.serialize_str(&format!("{}", self.semantics.texture_name()))
}
}
}
struct UniqueSemanticsVisitor;
impl<'de> Visitor<'de> for UniqueSemanticsVisitor {
type Value = Semantic<UniqueSemantics, ()>;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a valid uniform semantic name")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(match v {
"MVP" => Semantic {
semantics: UniqueSemantics::MVP,
index: (),
},
"OutputSize" => Semantic {
semantics: UniqueSemantics::Output,
index: (),
},
"FinalViewportSize" => Semantic {
semantics: UniqueSemantics::FinalViewport,
index: (),
},
"FrameCount" => Semantic {
semantics: UniqueSemantics::FrameCount,
index: (),
},
"FrameDirection" => Semantic {
semantics: UniqueSemantics::FrameDirection,
index: (),
},
"Rotation" => Semantic {
semantics: UniqueSemantics::Rotation,
index: (),
},
"TotalSubFrames" => Semantic {
semantics: UniqueSemantics::TotalSubFrames,
index: (),
},
"CurrentSubFrame" => Semantic {
semantics: UniqueSemantics::CurrentSubFrame,
index: (),
},
_ => return Err(E::custom(format!("unknown unique semantic {v}"))),
})
}
}
impl Serialize for Semantic<UniqueSemantics, ()> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.semantics.as_str())
}
}
impl<'de> Deserialize<'de> for Semantic<UniqueSemantics, ()> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(UniqueSemanticsVisitor)
}
}
}

View file

@ -3,7 +3,7 @@ name = "librashader-runtime-d3d11"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.3.0"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,20 +12,23 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[dependencies]
librashader-common = { path = "../librashader-common", features = ["d3d11"], version = "0.3.0" }
librashader-presets = { path = "../librashader-presets", version = "0.3.0" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.3.0" }
librashader-reflect = { path = "../librashader-reflect", version = "0.3.0" }
librashader-runtime = { path = "../librashader-runtime", version = "0.3.0" }
librashader-cache = { path = "../librashader-cache", version = "0.3.0", features = ["d3d"] }
librashader-common = { path = "../librashader-common", features = ["d3d11"], version = "0.5.1" }
librashader-presets = { path = "../librashader-presets", version = "0.5.1" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.5.1" }
librashader-pack = { path = "../librashader-pack", version = "0.5.1" }
librashader-reflect = { path = "../librashader-reflect", version = "0.5.1" }
librashader-runtime = { path = "../librashader-runtime", version = "0.5.1" }
librashader-cache = { path = "../librashader-cache", version = "0.5.1", features = ["d3d"] }
thiserror = "1.0.37"
bytemuck = "1.12.3"
rayon = "1.6.1"
array-concat = "0.5.2"
rayon = { workspace = true }
[features]
debug-shader = []
stable = ["librashader-reflect/stable"]
[target.'cfg(windows)'.dependencies.windows]
workspace = true

Some files were not shown because too many files have changed in this diff Show more