Compare commits

..

472 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
chyyran d1e49b7eb4 rt(d3d9): search "global" namespace for register assignments 2024-08-12 01:47:52 -04:00
chyyran 5ef0055e05 rt(d3d9): fix vertex assignments accidentally searching fragment 2024-08-12 01:47:52 -04:00
chyyran 1c6581d737 rt(d3d9): set colorwritemask to all 2024-08-12 01:47:52 -04:00
chyyran ac66b1b6f6 style: fix warnings 2024-08-09 00:56:44 -04:00
chyyran 977975f4c7 doc: document naga reflect 2024-08-09 00:56:44 -04:00
chyyran 3b9514ad38 doc: allow wgpu doc on apple 2024-08-09 00:56:44 -04:00
chyyran 899fb50da6 chore: Release 2024-08-03 00:13:40 -04:00
chyyran 162226ce44 ci: don't build locally
OSC seems very broken on Github Actions
2024-08-02 01:35:13 -04:00
chyyran 039fdfd41a ci: update OBS build platform 2024-08-02 01:26:31 -04:00
chyyran e13bb88df2 doc: update MSRV due to naga 2024-08-02 01:09:43 -04:00
chyyran 7b375658c5 dep: update cargo.lock 2024-08-02 01:04:56 -04:00
chyyran 3fb6e3843e dep: update to windows 0.58 2024-08-02 00:56:28 -04:00
chyyran 3cda5b706d rt(d3d12): get rid of size in inputview 2024-08-02 00:16:52 -04:00
chyyran 4d6793d305 rt(d3d11): get rid of input/output view wrappers 2024-08-02 00:16:52 -04:00
chyyran 35f499f5e1 wgpu: enable pipeline caching 2024-08-01 08:37:40 -04:00
chyyran 6ce711db26 ci: don't build Arch 2024-08-01 08:37:40 -04:00
chyyran d4b4366836 dep: update ash to 0.38 2024-08-01 08:37:40 -04:00
chyyran c646086df4 dep: update wgpu
need to support caching
2024-08-01 08:37:40 -04:00
chyyran f6cf642e50 chore: Release 2024-07-29 00:44:51 -04:00
chyyran 98958dfb5e dep: update rustc_hash 2024-07-29 00:40:59 -04:00
chyyran b5d523e9f3 rt(mtl): move icrate definitions to objc2-metal 2024-07-29 00:40:59 -04:00
chyyran 5e9ce1207c Revert "ci: temporarily remove deny-deprecated on ctypes to unblock ares"
This reverts commit a8d2d1d2ada8fd7e18ee55c53ce5115ef19d6154.
2024-07-29 00:40:59 -04:00
Hubert Hirtz 1b0574c140 presets: show file name and details on io error 2024-07-29 00:40:30 -04:00
Stefan Schlosser b84e104212 capi: fix malformed vulkan include 2024-07-29 00:40:21 -04:00
chyyran fff80df5a0 ci: temporarily remove deny-deprecated on ctypes to unblock ares 2024-06-14 18:17:36 -04:00
chyyran 875968d097 dep: update spirv_cross 2024-06-14 18:17:36 -04:00
chyyran fa48b936be rt: cap texture scaling to [1, 16384]
Fixes #79, #78
2024-06-14 18:17:36 -04:00
chyyran 0a9fa16855 rt: update for new TAIT scope rules 2024-06-14 18:17:36 -04:00
chyyran d558c6e50d dep: update image dependency 2024-06-14 18:17:36 -04:00
chyyran 48d91dfe58 dep: update image dependency 2024-06-14 18:17:36 -04:00
Isaac Marovitz d7665cac9b Update wgpu 2024-05-02 17:05:00 -04:00
chyyran e8ffd8fdf3 chore: Release 2024-03-08 00:18:30 -05:00
chyyran d893b6ec97 doc: update supported runtime table to include D3D9 and clarify wgpu status 2024-03-07 21:45:19 -05:00
chyyran 8c8e386a6c capi(d3d9): expose d3d9 in capi 2024-03-07 21:09:15 -05:00
chyyran b7071958bd rt(d3d9): add a runtime for direct3d 9 2024-03-07 21:09:15 -05:00
chyyran 5feac91af2 runtime: add ARGB8 pixel format 2024-03-07 21:09:15 -05:00
chyyran 9c895caa51 wgpu: fix format mismatch for copies 2024-03-07 21:09:15 -05:00
chyyran 7593f9f9b5 rt: pass device context to uniform binder if needed 2024-03-07 21:09:15 -05:00
chyyran af3ea252ba rt(d3d11): split luts out to own module 2024-03-07 21:09:15 -05:00
chyyran 9dc0cf26fd chore: Release 2024-03-05 19:04:14 -05:00
chyyran 2550bf7ed5 test: update shaders_slang 2024-03-05 19:03:54 -05:00
chyyran 35fc21bbef capi: fix mtl noop free fn name 2024-03-05 19:01:35 -05:00
chyyran 89b620a7c1 rt(gl46): fix invalid framebuffer copies 2024-03-05 19:01:35 -05:00
chyyran 66a0ee21e3 Revert "rt(gl): account for flipped coordinate space when blitting to output"
This reverts commit 623c6776f7.
2024-03-05 19:01:35 -05:00
chyyran 31b7a6f33f chore: Release 2024-03-03 13:21:29 -05:00
chyyran ed7216990a test: update slang_shaders 2024-03-03 13:19:55 -05:00
chyyran 623c6776f7 rt(gl): account for flipped coordinate space when blitting to output 2024-03-03 13:17:28 -05:00
chyyran bacfbf0791 cache: delete cache if corrupted 2024-03-03 13:17:28 -05:00
chyyran e02e1ae26a rt(gl): load luts with topleft origin due to MVP changes 2024-03-03 13:17:28 -05:00
chyyran d72519b9fd chore: Release 2024-03-01 01:41:50 -05:00
chyyran 752417f320 rt(vk): switch from an rwlock to a mutex for allocator 2024-02-28 18:30:59 -05:00
chyyran 10ad2d927c rt(d3d12): make descriptor heap lockfree 2024-02-28 18:30:59 -05:00
chyyran 8a9adebb96 rt(d3d12): upgrade to windows 0.52 2024-02-26 21:17:30 -05:00
chyyran 7719b939f9 rt(d3d11): upgrade to windows 0.52 2024-02-26 21:17:30 -05:00
chyyran a849f5e745 dep: unify on windows 0.52.0 2024-02-26 21:17:30 -05:00
chyyran f61bed3a22 vk: remove unneeded device arcs 2024-02-26 00:22:55 -05:00
chyyran 4ef4b8762b chore: Release 2024-02-24 12:38:13 -05:00
chyyran a5c684a7ee ci: parallelize tests 2024-02-24 12:26:37 -05:00
chyyran 8ba4b72cf1 ci: update github-ci and suppress info logs 2024-02-24 12:26:37 -05:00
chyyran 7a3a690166 reflect: improve error messages 2024-02-24 12:26:37 -05:00
chyyran b5bc3c11e1 ci: emit github warnings on failure 2024-02-24 12:26:37 -05:00
chyyran c7dd7796db ci: switch to dtolnay/rust-toolchain 2024-02-24 12:26:37 -05:00
chyyran 9741ab2cd1 ci: add a full test suite for reflecting things 2024-02-24 12:26:37 -05:00
chyyran b378c45039 lib: reexport MSL target 2024-02-24 01:48:55 -05:00
chyyran c7d1d347a4 rt: unify drawquad type 2024-02-22 01:16:42 -05:00
chyyran 95ac8adc20 rt(d3d12,d3d11): remove unused COLOR attribute in quad 2024-02-22 01:16:42 -05:00
chyyran 372315022d ci: pin toolchain used for aarch64-pc-windows-msvc 2024-02-20 19:26:43 -05:00
chyyran 699243c0ab dep: update Cargo.lock 2024-02-20 19:26:43 -05:00
chyyran 6d25a653a9 reflect: remove unused spirv-linker dependency 2024-02-20 19:26:43 -05:00
chyyran be11953516 build: allow passing arbitrary cargo flags 2024-02-20 19:26:43 -05:00
chyyran e38f2636d9 ci: build for aarch64-windows 2024-02-20 19:26:43 -05:00
chyyran f073c76ade chore: Release 2024-02-19 16:19:05 -05:00
chyyran 7ef3780222 fmt: cargo fmt 2024-02-19 12:39:44 -05:00
chyyran d60ff76fb2 ci: unify build workflow 2024-02-19 12:37:46 -05:00
chyyran 50aa582fa8 reflect(d3d12): fakesign dxil blobs to avoid needing dxil.dll 2024-02-19 09:54:19 -05:00
chyyran e8eee02bfb reflect: always link and trim unused inouts 2024-02-19 09:54:19 -05:00
chyyran 22aa59b598 reflect: move spirv_passes out of naga 2024-02-19 09:54:19 -05:00
chyyran b75a614873 doc(reflect): document ShaderReflectObject 2024-02-19 09:54:19 -05:00
chyyran a8ae407ddb chore: Release 2024-02-17 03:01:28 -05:00
Ronny Chan fe84e6a490 rt(gl): use identity matrix for intermediate GL passes 2024-02-17 03:00:53 -05:00
chyyran 913ede3852 rt(wgpu): set viewport depth to 0-1 2024-02-17 02:55:09 -05:00
chyyran 1f5b4380a3 chore: Release 2024-02-16 18:18:33 -05:00
chyyran ce3a8c6e52 doc: document msrv 2024-02-16 18:14:38 -05:00
chyyran c22328f025 build: revert ahash to 0.8.7 2024-02-16 18:07:41 -05:00
chyyran af49128ee7 chore: Release 2024-02-16 01:53:56 -05:00
chyyran d3d8e85461 capi: replace unstable library features with polyfills 2024-02-16 01:19:52 -05:00
chyyran 017a1a6232 fmt: clean up imports and features 2024-02-16 01:19:52 -05:00
chyyran e622479c76 build: ignore rust-version if using RUSTC_BOOTSTRAP 2024-02-16 01:19:52 -05:00
chyyran b47b27fadb rt(vk): update gpu-allocator 2024-02-16 01:19:52 -05:00
chyyran ba6c32e858 rt(wgpu): enable large thread size if possible 2024-02-15 21:43:02 -05:00
chyyran edca0f1749 rt(mtl): only gen mipmaps if the level count is greater than 1 2024-02-15 21:43:02 -05:00
chyyran efdfd56e0e rt(mtl): don't allow buffers of size 0 2024-02-15 21:43:02 -05:00
chyyran c0ecae844c reflect(wgsl): link spirv to remove unused input/outputs 2024-02-15 21:43:02 -05:00
chyyran cbac011969 reflect(wgsl): only analyze active ubo members 2024-02-15 21:43:02 -05:00
chyyran 350508a7aa preprocess: strip #pragma parameter 2024-02-15 21:43:02 -05:00
chyyran d6f1af8691 rt: fix tests 2024-02-15 21:43:02 -05:00
chyyran adeb9435fc ci: build on macos-14 runner 2024-02-14 21:41:08 -05:00
chyyran 499b8f5791 ci: make package-obs require approval from maintainers 2024-02-14 21:41:08 -05:00
chyyran e944330692 docs: add halfbrown as direct dependency of librashader for docs rendering 2024-02-14 21:28:21 -05:00
chyyran f7dd955c0a chore: Release 2024-02-14 20:51:57 -05:00
chyyran 227e6e743d pkg: remove runtime dependencies 2024-02-14 20:50:17 -05:00
chyyran 6fbc4b3075 fmt: cargo fmt 2024-02-14 20:50:17 -05:00
chyyran 05467c2c78 docs: update readme to include macOS binary refs 2024-02-14 20:50:17 -05:00
chyyran b7673de811 reflect: remove unneeded TAIT feature bound 2024-02-14 20:50:17 -05:00
chyyran 4247e64336 docs: update capi docs 2024-02-14 20:50:17 -05:00
chyyran b348e8591f lib: use a faster hashmap implementation 2024-02-14 20:50:17 -05:00
chyyran dc1ab35d89 doc: bump api version 2024-02-14 03:15:29 -05:00
chyyran 7f4a883288 doc: add some examples 2024-02-14 03:15:29 -05:00
chyyran a2987555a2 chore: Release 2024-02-14 03:08:40 -05:00
chyyran 3afcd6223c build(capi): dont enable all core features 2024-02-14 03:08:09 -05:00
chyyran fb62a1e3f4 build: downgrade ahash to 0.8.7 2024-02-14 03:08:09 -05:00
chyyran dca93a1310 test(mtl): add objc example 2024-02-14 03:08:09 -05:00
chyyran 4259b65ee0 doc(lib): hack to remove wgpu dependency on docsrs 2024-02-14 03:08:09 -05:00
chyyran f058134944 rt(wgpu): don't enable all backends by default 2024-02-14 03:08:09 -05:00
chyyran ad4e72f359 rt: use bytemuck::offset_of instead of std::mem::offset_of 2024-02-14 03:08:09 -05:00
chyyran 5c08205360 capi(ld): don't use designated initializer for loader 2024-02-14 03:08:09 -05:00
chyyran cc93e37701 reflect: get rid of redundant shader compiler argument 2024-02-14 03:08:09 -05:00
chyyran 76aa5ce4c6 capi(mtl): implement capi for metal 2024-02-14 03:08:09 -05:00
chyyran d89839be16 rt(msl): move main.rs test to tests folder 2024-02-14 03:08:09 -05:00
chyyran a1696813aa rt(mtl): optimize buffer usage on apple silicon gpus 2024-02-14 03:08:09 -05:00
chyyran 004b073b1a rt(mtl): reenable mipmaps 2024-02-14 03:08:09 -05:00
chyyran bceb0623a3 rt(mtl): implement clear textures 2024-02-14 03:08:09 -05:00
chyyran ab31abb3d7 capi(mtl): prep metal capi 2024-02-14 03:08:09 -05:00
chyyran 3b0531dc62 rt(mtl): rename librashader-runtime-metal to librashader-runtime-mtl to match existing convention and export from root crate 2024-02-14 03:08:09 -05:00
chyyran 363657deef rt(vk): make render passes the default, and dynamic rendering not.
This is technically a breaking change for the C API, but it doesn't break ABI.
If someone complains there is a migration guide anyways...
2024-02-14 03:08:09 -05:00
chyyran 05f634a9b9 rt(mtl): properly bind push buffer and select bgra8 in place of rgba8 2024-02-14 03:08:09 -05:00
chyyran 43da6e60c6 rt(mtl): do intermediate passes offscreen 2024-02-14 03:08:09 -05:00
chyyran 30dfa1a655 rt(mtl): update with new semantics 2024-02-14 03:08:09 -05:00
chyyran 325e39063a rt(mtl): move test to main 2024-02-14 03:08:09 -05:00
chyyran 5554703af7 rt(mtl): fix stride asssignment 2024-02-14 03:08:09 -05:00
chyyran a7b1682a37 rt(mtl): implement filter pass and filter chain logic 2024-02-14 03:08:09 -05:00
chyyran ba3154b92d rt(mtl): implement texture and buffer abstractions 2024-02-14 03:08:09 -05:00
chyyran 6780397d49 rt(mtl): fix build on windows 2024-02-14 03:08:09 -05:00
chyyran 1aedb1bea7 rt: auto-impl parameters 2024-02-14 03:08:09 -05:00
chyyran 8dc0e0d100 rt(mtl): set up pipeline objects and renderpass 2024-02-14 03:08:09 -05:00
chyyran f40df9a54a rt(mtl): drawquad 2024-02-14 03:08:09 -05:00
chyyran 12d55e928e rt(mtl): common + shaderset for metal 2024-02-14 03:08:09 -05:00
chyyran d5ef5904f3 cache: get rid of rusqlite to avoid a C dependency 2024-02-14 00:55:52 -05:00
chyyran aca5b5420c rt(gl): use struct for draw_quad 2024-02-13 02:01:20 -05:00
chyyran c121087348 rt(vk, wgpu): use structs for quad data 2024-02-13 02:01:20 -05:00
chyyran 2d98ebec1b rt(gl): fix tests 2024-02-13 02:01:20 -05:00
chyyran 849a749f1a reflect(cross-glsl): don't unset vertex input attributes 2024-02-13 02:01:20 -05:00
chyyran daf30c83c0 rt: add Rotation, TotalSubFrames, CurrentSubFrame uniform semantics 2024-02-12 01:58:05 -05:00
chyyran 3c3f024ef8 preset: add original scaletype
As defined in https://github.com/libretro/RetroArch/pull/15937
2024-02-12 01:58:05 -05:00
chyyran 4762055dc1 reflect(msl): naga msl implementation 2024-02-11 15:48:01 -05:00
chyyran d0a5224c10 dep: upgrade librashader-spirv-cross 2024-02-11 15:48:01 -05:00
chyyran 178790a202 ci: allow ubuntu to fail 2024-02-11 15:48:01 -05:00
chyyran 528dd1b53c reflect: fix some refs 2024-02-11 15:48:01 -05:00
chyyran a495b693a6 reflect(msl): implement spirv-cross msl 2024-02-11 15:48:01 -05:00
chyyran c67e9f4801 reflect: move folder structure around to be a little better 2024-02-11 15:48:01 -05:00
chyyran e1f62fc984 reflect: remove ShaderOutputCompiler and just delegate to FromCompilation 2024-02-11 15:48:01 -05:00
chyyran b98d86a940 reflect: allow specifying output toolchain 2024-02-11 15:48:01 -05:00
chyyran 252f685967 reflect: abstract away output compiler into its own trait 2024-02-11 15:48:01 -05:00
chyyran a7ca391ef6 reflect: abstract away input compiler from compilation 2024-02-11 15:48:01 -05:00
chyyran 11d12730eb rt(wgpu): don't use rayon on wasm32
doesn't build on wasm32 because missing glslang but that should be the only snag now
2024-02-11 15:48:01 -05:00
chyyran 4733831500 rt(vk): update winit in tests 2024-02-11 15:48:01 -05:00
chyyran abbec84594 rt(d3d12): rename quad_render to draw_quad 2024-02-11 15:48:01 -05:00
chyyran 12af3c3f3a chore: Release 2024-02-09 18:30:07 -05:00
Ronny Chan b9a6b869e3
Merge pull request #37 from SnowflakePowered/preset-context
Preset context
2024-02-09 18:29:11 -05:00
chyyran fa8ee5d143 build: fix cbindgen not finding ctypes
* removes capi internal interface from librashader crate
* adds missing noop impls in header
* fix build on rustc < 1.74
2024-02-09 18:02:30 -05:00
chyyran 8f89b3e720 doc(preset): document preset contexts 2024-02-09 03:07:23 -05:00
chyyran c34fa4195b chore: Release 2024-02-09 02:55:02 -05:00
chyyran 2fbc7f92da capi: add preset_ctx C API 2024-02-09 02:49:31 -05:00
chyyran 9732812b91 build: fix build script clap to 4.1.0 2024-02-09 02:02:13 -05:00
chyyran 4da6c98655 rt: add driver context + path context for FilterChain::load_with_path 2024-02-09 02:02:13 -05:00
chyyran a14b36e05b presets: initial preset contexts API 2024-02-09 02:02:13 -05:00
chyyran b2d8d084be test: update shaders_slang 2024-02-09 02:02:13 -05:00
chyyran f1524f6049 preset: initial work on context 2024-02-09 02:02:13 -05:00
Ronny Chan 6d4e6590de
Merge pull request #36 from LukeUsher/master
build: support building for macOS and non-linux unixes
2024-02-08 21:16:20 -05:00
chyyran e776ee2823 ci: only do OBS tasks on origin 2024-02-08 18:16:10 -05:00
chyyran 3d74f27d77 rt(vk): fix double free in RawVulkanBuffer 2024-02-08 18:16:10 -05:00
chyyran f9fdb93c0c dep: update glslang and bitflags 2024-02-08 18:16:10 -05:00
Luke Usher 7f0f985a14 support building for macOS and non-linux unixes
Also updates build.yml to add macOS runners
2024-02-08 16:31:05 +00:00
chyyran b7f62dc378 chore: Release 2024-02-07 23:56:09 -05:00
chyyran b796494cc6 build: remove libvulkan buildtime dependency 2024-02-07 23:55:33 -05:00
chyyran 91794dd353 build: remove cmake and python dependency 2024-02-07 23:55:33 -05:00
chyyran 2b208f1848 dep: remove shaderc dependency 2024-02-07 23:55:33 -05:00
chyyran 47f6e0f10e chore: Release 2024-02-07 00:00:18 -05:00
chyyran 665570342c doc(lib): add wgpu-types to librashader deps 2024-02-06 23:59:55 -05:00
chyyran fb2bcc5d52 chore: Release 2024-02-06 21:41:00 -05:00
chyyran 8015a2a796 fmt: cleanup repo 2024-02-06 21:38:17 -05:00
chyyran 5c8428eac8 lib: add wgpu dependency in runtime-wgpu 2024-02-06 21:38:17 -05:00
chyyran 8fb2179ae8 lib: add wgpu dependency in runtime-wgpu 2024-02-06 21:31:00 -05:00
chyyran f6268a621c chore: Release 2024-02-06 19:20:37 -05:00
chyyran bbfd5153da rt(wgpu): rename OutputView to WgpuOutputView 2024-02-06 19:04:28 -05:00
chyyran 37397ff216 lib: add wgpu to librashader library 2024-02-06 19:04:28 -05:00
chyyran 754e8da620 runtime: remove unused AsDerefable trait 2024-02-06 19:04:28 -05:00
chyyran 6c50880600 reflect: remove spirt test outputs 2024-02-06 19:04:28 -05:00
chyyran acc9bfeb53 rt(wgpu): add blocking submission API 2024-02-06 19:04:28 -05:00
chyyran 54e86e7b06 fmt: clean up unused imports 2024-02-06 19:04:28 -05:00
chyyran 2450217c29 fmt: cargo fmt 2024-02-06 19:04:28 -05:00
chyyran 962a81c2e3 rt(wgsl): mipmaps 2024-02-06 19:04:28 -05:00
chyyran 11ab4b7c9a build: tag RPITIT cause ubuntu 23.10 uses an older compiler 2024-02-06 19:04:28 -05:00
chyyran 121dbc4ed6 reflect(wgsl): properly adjust coordinate space to WGSL-expected 2024-02-06 19:04:28 -05:00
chyyran 31891e414f rt(wgpu): fix enough stuff to get it to draw a frame 2024-02-06 19:04:28 -05:00
chyyran e39834547c rt(wgpu): take an arc of the source texture 2024-02-06 19:04:28 -05:00
chyyran d5aa6b2e4a rt(wgpu): sketch out skeleton for filter chain logic 2024-02-06 19:04:28 -05:00
chyyran cc26be486b rt(wgpu): fix compiler errors for filer pass 2024-02-06 19:04:28 -05:00
chyyran 10358b4966 rt(wgpu): wip filter chain logic 2024-02-06 19:04:28 -05:00
chyyran 32148cdff4 rt(wgpu): filter pass logic 2024-02-06 19:04:28 -05:00
chyyran 555ff6f9fc rt(wgpu): update to wgpu 0.19 2024-02-06 19:04:28 -05:00
chyyran 34f224cc5d build: try to fix build for arm 2024-02-06 19:04:28 -05:00
chyyran 7586ed4633 build: don't specify python patch
# Conflicts:
#	.idea/workspace.xml
2024-02-06 19:04:28 -05:00
chyyran 2b995539f2 rt(wgpu): add structure to wgpu backend 2024-02-06 19:04:28 -05:00
chyyran 8cfe1e9da2 rt(wgpu): create pipeline 2024-02-06 19:04:28 -05:00
chyyran f9df72a02d rt(wgpu): sampler set 2024-02-06 19:04:28 -05:00
chyyran 4e052159e7 rt(wgpu): create pipeline bind group layouts 2024-02-06 19:04:28 -05:00
chyyran 171c842c97 reflect(wgsl): implement WGSL reflection 2024-02-06 19:04:28 -05:00
chyyran 4dfcdf2725 reflect(wgsl): wgsl compile backend 2024-02-06 19:04:28 -05:00
chyyran 1a16c4fadf rt(wgpu): load shaders 2024-02-06 19:04:28 -05:00
chyyran c05d8ff06a rt(wgpu): basic triangle example 2024-02-06 19:04:28 -05:00
Ronny Chan ec98494202
build(obs): remove old obscpio before pushing 2024-02-04 18:06:39 -05:00
Ronny Chan ef4e2353ff
build(obs): remove old oscpio 2024-02-04 18:05:58 -05:00
chyyran a5e978f158 doc: add download link 2024-02-04 13:16:48 -05:00
chyyran 97ff76276f doc: add obs badge 2024-02-04 13:16:48 -05:00
chyyran 797625903a build(obs): add openbuildservice 2024-02-04 13:16:48 -05:00
Ronny Chan 81ba694ba4 build(copr): require git 2024-02-04 13:16:48 -05:00
chyyran 18ff9cf05a build(copr): add copr 2024-02-04 13:16:48 -05:00
chyyran ae2a427b5e pkg(rpm): add RPM spec file 2024-02-04 13:16:48 -05:00
chyyran 552be8c34e rt(gl): remove unnecessary unstable features
polyfill strict_provenance with sptr
2024-02-03 22:06:55 -05:00
chyyran 7c0190004f rt(d3d12): remove unnecessary unstable features
div_ceil has been stabilized, and we don't really make use of const trait impl
2024-02-03 22:06:55 -05:00
chyyran a6c91a07df build: use stable polyfills for extract_if and array_chunks_mut 2024-02-03 22:06:55 -05:00
chyyran d700234c3c presets: polyfill extract_if 2024-02-03 22:06:55 -05:00
chyyran 96f937586c build: clean unused features 2024-02-03 03:01:16 -05:00
chyyran 9c5a8f4042 gh: fix python requirement 2024-02-03 02:46:18 -05:00
chyyran d5bf7e312c build: update lockfile 2024-02-03 02:42:52 -05:00
chyyran 80325fda9e fmt: cargo fmt 2024-02-03 02:42:52 -05:00
chyyran 60fac06332 build: fix rename when pdb is missing 2024-02-03 02:42:52 -05:00
chyyran 92e8a05f8a build: fix cbindgen version 2024-02-03 02:42:52 -05:00
chyyran 617bfdd93e ide: fix rustrover iml 2024-02-03 02:42:52 -05:00
chyyran fa3b6bf5fc chore: Release 2023-11-30 02:11:33 -05:00
chyyran ee0587310c chore: Release 2023-11-30 02:07:12 -05:00
chyyran 2bd6f8f80f build: prefer static linking of shaderc 2023-11-30 02:03:43 -05:00
chyyran d17503be71 build: unify ash version 2023-11-30 02:03:43 -05:00
chyyran 2be2178502 build: allow aarch64-linux-unknown-gnu build of librashader-capi 2023-11-29 03:26:58 -05:00
chyyran f4bdf160ab build: only build dxil on windows 2023-11-29 03:26:58 -05:00
chyyran c0a1b56f4e build: use build deps for linux 2023-11-29 03:26:58 -05:00
chyyran 43c0f3d676 build: fix python 3.11 2023-11-29 03:26:58 -05:00
chyyran 25bf4904e1 build: use install-vulkan-sdk 2023-11-29 03:26:58 -05:00
chyyran 7ce02014d4 build: upgrade python first 2023-11-29 03:26:58 -05:00
chyyran 6e071138dc build: update spirv-to-dxil-sys to reduce mesa build reqs 2023-11-29 03:26:58 -05:00
chyyran 7fbf3e23f0 ci: fix windows build since spirv-to-dxil needs bison now 2023-07-20 02:40:13 -04:00
chyyran f5da7d8421 fmt: cargo fmt 2023-07-20 02:40:13 -04:00
chyyran b09a5295ab reflect: update spirv-to-dxil 2023-07-20 02:40:13 -04:00
chyyran 59cc3deb09 rt: fix TAIT usages 2023-07-20 02:40:13 -04:00
chyyran 3735659604 preset: drain_filter -> extract_if 2023-07-20 02:40:13 -04:00
chyyran ab8072c4f7 chore: Release 2023-04-23 18:52:14 -04:00
chyyran 24f28bb605 capi: better clarify story around panic safety
- catches panics for all functions now except frame for performance reasons
2023-04-23 02:09:04 -04:00
chyyran 916cd1a681 doc: fix doc typos 2023-04-23 01:44:28 -04:00
chyyran 020fac87ba deps: upgrade to windows 0.48.0 2023-04-23 01:44:28 -04:00
chyyran 70aa4091e0 build: fix nightly flag 2023-04-23 00:13:39 -04:00
chyyran 3e144bbdff presets: allow unbalanced quotes 2023-02-24 02:11:52 -05:00
chyyran 31235971db chore: Release 2023-02-21 21:07:17 -05:00
chyyran 650aecbe10 d3d11/12: fix sampler set creation 2023-02-21 21:06:47 -05:00
chyyran ac1494bee1 chore: Release 2023-02-21 17:09:10 -05:00
chyyran ded8851e16 d3d11/12: fix sampler set creation 2023-02-21 17:08:24 -05:00
341 changed files with 35028 additions and 8583 deletions

View file

@ -14,34 +14,138 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
profile: ['debug', 'release', 'optimized']
os: ['windows-latest', 'ubuntu-latest', 'macos-latest', 'macos-14']
include:
- os: ubuntu-latest
output: x86_64-ubuntu
- os: windows-latest
output: x86_64-windows
- os: macos-latest
output: x86_64-macos
- os: macos-14
output: aarch64-macos
fail-fast: false
runs-on: ${{ matrix.os }}
name: ${{ matrix.output }} (${{ matrix.profile }})
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install nightly Rust
uses: actions-rs/toolchain@v1.0.6
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
override: true
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
with:
vulkan-query-version: latest
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Cross, SPIRV-Headers, SPIRV-Reflect, SPIRV-Tools, Glslang
vulkan-use-cache: true
- uses: actions/setup-python@v1
name: Setup Python
with:
python-version: '3.x'
- run: pip install meson ninja mako
name: Install Meson for spirv-to-dxil-sys
- 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('build-outputs-{0}-{1}', matrix.os, matrix.profile) }}
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:
profile: ['debug', 'release', 'optimized']
fail-fast: false
runs-on: ubuntu-latest
name: aarch64-ubuntu
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
targets: aarch64-unknown-linux-gnu
- name: Install ARM64 cross-compilation dependencies
continue-on-error: true
run: |
sudo apt-get update || true
sudo dpkg --add-architecture arm64
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 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@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:
profile: ['debug', 'release', 'optimized']
fail-fast: false
runs-on: windows-latest
name: aarch64-windows
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@master
with:
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@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) }}

40
.github/workflows/publish-obs.yml vendored Normal file
View file

@ -0,0 +1,40 @@
name: publish packages using Open Build Service
on:
push:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
jobs:
publish-obs:
if: github.repository == 'SnowflakePowered/librashader'
runs-on: ubuntu-latest
container: fedora:39
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/
rm *.obscpio
osc service mr
- name: Commit source artifacts to Open Build Service
run: |
cd home:chyyran:librashader/librashader/
osc ar
osc commit -f -m "git-rev ${{ github.sha }}"

67
.github/workflows/push-full-test.yml vendored Normal file
View file

@ -0,0 +1,67 @@
name: integration test shader reflection
on:
push:
branches: [ "master" ]
schedule:
- cron: "0 0 * * 6"
env:
CARGO_TERM_COLOR: always
jobs:
test-presets:
runs-on: ubuntu-latest
continue-on-error: false
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 preprocessing
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
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
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 reflection
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
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

2
.gitignore vendored
View file

@ -5,6 +5,7 @@
*.rdc
*.cap
/.vs/
.idea/
librashader_runtime_*.exe
/test/capi-tests/librashader-capi-tests/.vs/
/test/capi-tests/librashader-capi-tests/x64/
@ -12,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

8
.idea/.gitignore vendored
View file

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace">
<contentRoot DIR="$PROJECT_DIR$" />
</component>
</project>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/src.iml" filepath="$PROJECT_DIR$/.idea/src.iml" />
</modules>
</component>
</project>

View file

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/test/shaders_slang" vcs="Git" />
</component>
</project>

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

3613
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,12 +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-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.**

186
README.md
View file

@ -1,30 +1,43 @@
# 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>
librashader (*/ˈli:brəʃeɪdɚ/*) is a preprocessor, compiler, and runtime for RetroArch 'slang' shaders, rewritten in pure Rust.
[![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) ![License](https://img.shields.io/crates/l/librashader)
![Nightly rust](https://img.shields.io/badge/rust-nightly-orange.svg)
[![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)
![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.
Windows and macOS users can grab the latest binaries from [GitHub Releases](https://github.com/SnowflakePowered/librashader/releases).
## Supported Render APIs
librashader supports OpenGL 3, OpenGL 4.6, Vulkan, Direct3D 11, and Direct3D 12. Metal and WebGPU
are not currently supported (but pull-requests are welcome). librashader does not support legacy render
APIs such as older versions of OpenGL, or legacy versions of Direct3D.
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 limited
support for Direct3D 9.
| **API** | **Status** | **`librashader` feature** |
|-------------|------------|--------------------------|
| OpenGL 3.3+ | ✔ | `gl` |
| OpenGL 4.6 | ✔ | `gl` |
| Vulkan | ✔ | `vk` |
| Direct3D 11 | ✔ | `d3d11` |
| Direct3D 12 | ✔ | `d3d12` |
| Metal | ❌ | |
| WebGPU | ❌ | |
| OpenGL 3.3+ | ✅ | `gl` |
| OpenGL 4.6 | ✅ | `gl` |
| Vulkan | ✅ | `vk` |
| Direct3D 9 | 🆗️ |`d3d9` |
| Direct3D 11 | ✅ | `d3d11` |
| Direct3D 12 | ✅ | `d3d12` |
| Metal | ✅ | `metal` |
| wgpu | 🆗 | `wgpu` |
✔ = Render API is supported &mdash; ❌ Render API is not supported
✅ Full Support &mdash; 🆗 Secondary Support
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
@ -33,20 +46,20 @@ The C API is geared more towards integration with existing projects. The Rust `l
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` or `librashader.dll`) implementation in the search path.
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`. See the [versioning policy](https://github.com/SnowflakePowered/librashader#versioning)
for details on how librashader handles C ABI and API stability with regards to library updates.
a dynamic loader for `librashader.dll` / `librashader.so` / `librashader.dylib`. See the [versioning policy](https://github.com/SnowflakePowered/librashader#versioning)
for details on how librashader handles C ABI and API stability with regards to library updates. You can also link dynamically
with just `librashader.h` and the equivalent of `-lrashader`.
Linking statically against `librashader.h` is possible, but is not officially supported. You will need to ensure
linkage parameters are correct in order to successfully link with `librashader.lib` or `librashader.a`.
The [corrosion](https://github.com/corrosion-rs/) CMake package is highly recommended.
### Thread safety
In general, it is **safe** to create a filter chain instance from a different thread, but drawing frames requires
Except for the Metal runtime, in general, it is **safe** to create a filter chain instance from a different thread, but drawing frames requires
**external synchronization** of the filter chain object.
Filter chains can be created from any thread, but requires external synchronization of the graphics device queue where applicable
@ -58,8 +71,13 @@ OpenGL has an additional restriction where creating the filter chain instance in
the thread local OpenGL context is initialized to the same context as the drawing thread. Support for deferral of GPU resource initialization
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 except OpenGL render with an identity matrix MVP and a VBO for with range `[-1, 1]`. The final pass uses a
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.
```rust
@ -74,27 +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.
The OpenGL runtime uses a VBO for range `[0, 1]` for all passes and the following MVP for all passes.
### Writing a librashader Runtime
```rust
static GL_DEFAULT_MVP: &[f32; 16] = &[
2.0, 0.0, 0.0, 0.0,
0.0, 2.0, 0.0, 0.0,
0.0, 0.0, 2.0, 0.0,
-1.0, -1.0, 0.0, 1.0,
];
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
```
### Building
For more information, see [`CLI.md`](https://github.com/SnowflakePowered/librashader/blob/master/CLI.md).
librashader requires the following build time dependencies
* The [Vulkan SDK](https://www.lunarg.com/vulkan-sdk/)
* [Meson](https://mesonbuild.com/)
* [CMake 3.8 or later](https://cmake.org/)
* [Python 3.6 or later](https://www.python.org/)
---
## Building
For Rust projects, simply add the crate to your `Cargo.toml`.
@ -111,14 +141,31 @@ cargo run -p librashader-build-script -- --profile optimized
This will output a `librashader.dll` or `librashader.so` in the target folder. Profile can be `debug`, `release`, or
`optimized` for full LTO.
### Writing a librashader Runtime
While librashader has no build-time dependencies, using `librashader_ld.h` may require headers from
the relevant runtime graphics API.
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.
### 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.
```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,9 +174,13 @@ The following Rust examples show how to use each librashader runtime.
* [OpenGL](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-gl/tests/triangle.rs)
* [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 in the [librashader-capi-tests](https://github.com/SnowflakePowered/librashader/tree/master/test/capi-tests/librashader-capi-tests)
directory.
Some basic examples on using the C API are also provided.
* [Direct3D 11](https://github.com/SnowflakePowered/librashader/tree/master/test/capi-tests/librashader-capi-tests)
* [Metal with Objective-C](https://github.com/SnowflakePowered/librashader/tree/master/test/capi-tests/objctest)
## Compatibility
@ -149,20 +200,25 @@ 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.
* Sampler objects are used rather than `glTexParameter`.
* Sampler inputs and outputs are not renamed. This is useful for debugging shaders in RenderDoc.
* UBO and Push Constant Buffer sizes are padded to 16-byte boundaries.
* The OpenGL runtime uses the same VBOs as the other runtimes as well as the identity matrix MVP for intermediate passes. RetroArch's OpenGL driver uses only the final VBO.
* OpenGL 4.6+
* All caveats from the OpenGL 3.3+ section should be considered.
* Should work on OpenGL 4.5 but this is not guaranteed. The OpenGL 4.6 runtime may eventually switch to using `ARB_spirv_extensions` for loading shaders, and this will not be marked as a breaking change.
* The OpenGL 4.6 runtime uses Direct State Access to minimize changes to the OpenGL state. For GPUs released within the last 5 years, this may improve performance.
* The OpenGL runtime uses the same VBOs as the other runtimes as well as the identity matrix MVP for intermediate passes. RetroArch's OpenGL driver uses only the final VBO.
* Vulkan
* The Vulkan runtime uses [`VK_KHR_dynamic_rendering`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_dynamic_rendering.html) by default.
This extension must be enabled at device creation. Explicit render passes can be used by configuring filter chain options, but may have reduced performance
compared to dynamic rendering.
* The Vulkan runtime can use [`VK_KHR_dynamic_rendering`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_dynamic_rendering.html).
This extension must be enabled at device creation.
Dynamic rendering may have improved performance when enabled, and supported by the host hardware.
* Allocations within the runtime are done through [gpu-allocator](https://github.com/Traverse-Research/gpu-allocator) rather than handled manually.
* Direct3D 11
* Framebuffer copies are done via `ID3D11DeviceContext::CopySubresourceRegion` rather than a CPU conversion + copy.
@ -171,15 +227,17 @@ Please report an issue if you run into a shader that works in RetroArch, but not
which was released in late 2018.
* For maximum compatibility with shaders, a shader compile pipeline based on [`spirv-to-dxil`](https://github.com/SnowflakePowered/spirv-to-dxil-rs) is used, with the SPIRV-Cross HLSL pipeline used as a fallback.
This brings shader compatibility beyond what the RetroArch Direct3D 12 driver provides. The HLSL pipeline fallback may be removed in the future as `spirv-to-dxil` improves.
* The Direct3D 12 runtime requires `dxil.dll` and `dxcompiler.dll` from the [DirectX Shader Compiler](https://github.com/microsoft/DirectXShaderCompiler).
* The Direct3D 12 runtime requires `dxcompiler.dll` from the [DirectX Shader Compiler](https://github.com/microsoft/DirectXShaderCompiler), which may already be installed as part of Direct3D12. `dxil.dll` is not required.
* Metal
* The Metal runtime uses the same VBOs as the other runtimes as well as the identity matrix MVP for intermediate passes. RetroArch's Metal driver uses only the final VBO.
Most, if not all shader presets should work fine on librashader. The runtime specific differences should not affect the output,
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 API](https://img.shields.io/badge/API%20version-0-blue)
![C ABI](https://img.shields.io/badge/ABI%20version-2-yellowgreen)
![C API](https://img.shields.io/badge/API%20version-1-blue)
librashader typically follows [Semantic Versioning](https://semver.org/) with respect to the Rust API, where a minor version
@ -206,9 +264,29 @@ will check to ensure that `LIBRASHADER_CURRENT_ABI` matches that of the loaded l
librashader will not load. A value of `0` for `LIBRASHADER_CURRENT_ABI` indicates the "null" instance where every operation
is a no-op, which occurs if no compatible librashader implementation could be found.
The `SONAME` of `librashader.so` when installed via package manager is set to `LIBRASHADER_CURRENT_ABI`.
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
Building against stable Rust requires the following MSRV.
* All platforms: **1.78**
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,
the reflection library, and the runtimes, are all licensed under the Mozilla Public License version 2.0.
@ -218,13 +296,13 @@ are more permissively licensed, and may allow you to use librashader in your per
licensed or proprietary project.
To facilitate easier use of librashader in projects incompatible with MPL-2.0, `librashader_ld`
implements a loader which thunks its calls to any `librashader.so` or `librashader.dll`
implements a loader which thunks its calls to any `librashader.so`, `librashader.dll`, or `librashader.dylib`.
library found in the load path. A non-MPL-2.0 compatible project may link against
`librashader_ld` to use the librashader runtime, *provided that `librashader.so` or `librashader.dll`
`librashader_ld` to use the librashader runtime, *provided that `librashader.so`, `librashader.dll` or `librashader.dylib`
are distributed under the restrictions of MPLv2*.
Note that this means that if your project is unable to comply with the requirements of MPL-2.0,
you **can not distribute `librashader.so` or `librashader.dll`** alongside your project.
you **can not distribute `librashader.so`, `librashader.dll` or `librashader.dylib`** alongside your project.
The end user must obtain the implementation of librashader themselves. For more information,
see the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/).

View file

@ -34,14 +34,8 @@ libra_gl_filter_chain_t load_gl_filter_chain(libra_gl_loader_t opengl, const cha
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;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,9 @@ publish = false
[dependencies]
cbindgen = { git = "https://github.com/eqrion/cbindgen" }
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,8 +1,9 @@
use carlog::*;
use clap::Parser;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::process::{Command, ExitCode};
use std::{env, fs};
#[derive(Parser, Debug)]
@ -10,12 +11,18 @@ use std::{env, fs};
struct Args {
#[arg(long, default_value = "debug", global = true)]
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>,
}
pub fn main() {
pub fn main() -> ExitCode {
// Do not update files on docsrs
if env::var("DOCS_RS").is_ok() {
return;
return ExitCode::SUCCESS;
}
let args = Args::parse();
@ -23,43 +30,122 @@ pub fn main() {
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!(
"--profile={}",
if profile == "debug" { "dev" } else { &profile }
));
Some(cmd.status().expect("Failed to build librashader-capi"));
let output_dir = PathBuf::from(format!("target/{}", profile))
.canonicalize()
.expect("Could not find output directory.");
// If we're on RUSTC_BOOTSTRAP, it's likely because we're building for a package..
if env::var("RUSTC_BOOTSTRAP").is_ok() {
cmd.arg("--ignore-rust-version");
}
println!("Generating C headers...");
if let Some(target) = &args.target {
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 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);
}
let mut output_dir = PathBuf::from(format!("target/{}", profile));
if let Some(target) = &args.target {
output_dir = PathBuf::from(format!("target/{}/{}", target, profile));
}
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;
};
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);
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;
};
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");
File::create(output_dir.join("librashader.h"))
.expect("Unable to open file")
.write_all(string.as_bytes())
.expect("Unable to write bindings.");
println!("Moving artifacts...");
if cfg!(target_os = "linux") {
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", "");
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));
}
}
@ -70,11 +156,29 @@ pub fn main() {
"librashader_capi.d",
"librashader_capi.dll.exp",
"librashader_capi.dll.lib",
"librashader_capi.pdb",
];
for artifact in artifacts {
let ext = artifact.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));
}
if output_dir.join("librashader_capi.pdb").exists() {
fs::rename(
output_dir.join("librashader_capi.pdb"),
output_dir.join("librashader.pdb"),
)
.unwrap();
carlog_ok!("Renamed", "librashader_capi.pdb to librashader.pdb");
}
}
ExitCode::SUCCESS
}

View file

@ -2,7 +2,7 @@
name = "librashader-cache"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.1.1"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,18 +12,22 @@ description = "RetroArch shaders for all."
[dependencies]
serde = { version = "1.0" }
librashader-reflect = { path = "../librashader-reflect", version = "0.1.1", features = ["serialize", "dxil"] }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.1.1" }
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"] }
rusqlite = { version = "0.28.0", features = ["bundled"] }
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]
version = "0.44.0"
workspace = true
features = [
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D_Fxc",
@ -32,10 +36,10 @@ features = [
optional = true
[features]
d3d = ["windows"]
d3d = ["windows", "librashader-reflect/dxil"]
# hack to get building on docsrs
docsrs = ["blake3/pure", "rusqlite/in_gecko"]
docsrs = ["blake3/pure"]
[package.metadata.docs.rs]
features = ["docsrs"]

View file

@ -3,10 +3,11 @@ use crate::key::CacheKey;
pub(crate) mod internal {
use platform_dirs::AppDirs;
use rusqlite::{Connection, DatabaseName};
use std::error::Error;
use std::path::PathBuf;
use persy::{ByteVec, Config, Persy, ValueMode};
pub(crate) fn get_cache_dir() -> Result<PathBuf, Box<dyn Error>> {
let cache_dir = if let Some(cache_dir) =
AppDirs::new(Some("librashader"), false).map(|a| a.cache_dir)
@ -23,46 +24,73 @@ pub(crate) mod internal {
Ok(cache_dir)
}
pub(crate) fn get_cache() -> Result<Connection, Box<dyn Error>> {
let cache_dir = get_cache_dir()?;
let mut conn = Connection::open(&cache_dir.join("librashader.db"))?;
// pub(crate) fn get_cache() -> Result<Connection, Box<dyn Error>> {
// let cache_dir = get_cache_dir()?;
// let mut conn = Connection::open(&cache_dir.join("librashader.db"))?;
//
// let tx = conn.transaction()?;
// tx.pragma_update(Some(DatabaseName::Main), "journal_mode", "wal2")?;
// tx.execute(
// r#"create table if not exists cache (
// type text not null,
// id blob not null,
// value blob not null unique,
// primary key (id, type)
// )"#,
// [],
// )?;
// tx.commit()?;
// Ok(conn)
// }
let tx = conn.transaction()?;
tx.pragma_update(Some(DatabaseName::Main), "journal_mode", "wal2")?;
tx.execute(
r#"create table if not exists cache (
type text not null,
id blob not null,
value blob not null unique,
primary key (id, type)
)"#,
[],
)?;
pub(crate) fn get_cache() -> Result<Persy, Box<dyn Error>> {
let cache_dir = get_cache_dir()?;
match Persy::open_or_create_with(
&cache_dir.join("librashader.db.1"),
Config::new(),
|persy| {
let tx = persy.begin()?;
tx.commit()?;
Ok(conn)
Ok(())
},
) {
Ok(conn) => Ok(conn),
Err(e) => {
let path = &cache_dir.join("librashader.db.1");
let _ = std::fs::remove_file(path).ok();
Err(e)?
}
}
}
pub(crate) fn get_blob(
conn: &Connection,
conn: &Persy,
index: &str,
key: &[u8],
) -> Result<Vec<u8>, Box<dyn Error>> {
let value = conn.query_row(
&*format!("select value from cache where (type = (?1) and id = (?2))"),
rusqlite::params![index, key],
|row| row.get(0),
)?;
Ok(value)
) -> Result<Option<Vec<u8>>, Box<dyn Error>> {
if !conn.exists_index(index)? {
return Ok(None);
}
pub(crate) fn set_blob(conn: &Connection, index: &str, key: &[u8], value: &[u8]) {
match conn.execute(
&*format!("insert or replace into cache (type, id, value) values (?1, ?2, ?3)"),
rusqlite::params![index, key, value],
) {
Ok(_) => return,
Err(e) => println!("err: {:?}", e),
let value = conn.get::<_, ByteVec>(index, &ByteVec::from(key))?.next();
Ok(value.map(|v| v.to_vec()))
}
pub(crate) fn set_blob(
conn: &Persy,
index: &str,
key: &[u8],
value: &[u8],
) -> Result<(), Box<dyn Error>> {
let mut tx = conn.begin()?;
if !tx.exists_index(index)? {
tx.create_index::<ByteVec, ByteVec>(index, ValueMode::Replace)?;
}
tx.put(index, ByteVec::from(key), ByteVec::from(value))?;
tx.commit()?;
Ok(())
}
}
@ -101,7 +129,7 @@ where
};
'attempt: {
if let Ok(blob) = internal::get_blob(&cache, index, hashkey.as_bytes()) {
if let Ok(Some(blob)) = internal::get_blob(&cache, index, hashkey.as_bytes()) {
let cached = T::from_bytes(&blob).map(&load);
match cached {
@ -115,7 +143,7 @@ where
let blob = factory(keys)?;
if let Some(slice) = T::to_bytes(&blob) {
internal::set_blob(&cache, index, hashkey.as_bytes(), &slice);
let _ = internal::set_blob(&cache, index, hashkey.as_bytes(), &slice);
}
Ok(load(blob)?)
}
@ -157,7 +185,7 @@ where
};
let pipeline = 'attempt: {
if let Ok(blob) = internal::get_blob(&cache, index, hashkey.as_bytes()) {
if let Ok(Some(blob)) = internal::get_blob(&cache, index, hashkey.as_bytes()) {
let cached = restore_pipeline(Some(blob));
match cached {
Ok(res) => {
@ -173,7 +201,8 @@ where
// update the pso every time just in case.
if let Ok(state) = fetch_pipeline_state(&pipeline) {
if let Some(slice) = T::to_bytes(&state) {
internal::set_blob(&cache, index, hashkey.as_bytes(), &slice);
// We don't really care if the transaction fails, just try again next time.
let _ = internal::set_blob(&cache, index, hashkey.as_bytes(), &slice);
}
}

View file

@ -16,3 +16,13 @@ impl Cacheable for Vec<u8> {
Some(self.to_vec())
}
}
impl Cacheable for Option<Vec<u8>> {
fn from_bytes(bytes: &[u8]) -> Option<Self> {
Some(Some(Vec::from(bytes)))
}
fn to_bytes(&self) -> Option<Vec<u8>> {
self.clone()
}
}

View file

@ -1,24 +1,35 @@
//! Cache helpers for `ShaderCompilation` objects to cache compiled SPIRV.
use librashader_preprocess::ShaderSource;
use librashader_reflect::back::targets::{DXIL, GLSL, HLSL, SPIRV};
#[cfg(all(target_os = "windows", feature = "d3d"))]
use librashader_reflect::back::targets::DXIL;
use librashader_reflect::back::targets::{GLSL, HLSL, SPIRV};
use librashader_reflect::back::{CompilerBackend, FromCompilation};
use librashader_reflect::error::{ShaderCompileError, ShaderReflectError};
use librashader_reflect::front::{GlslangCompilation, ShaderCompilation};
use librashader_reflect::front::{
Glslang, ShaderInputCompiler, ShaderReflectObject, SpirvCompilation,
};
pub struct CachedCompilation<T> {
compilation: T,
}
impl<T: ShaderCompilation + for<'de> serde::Deserialize<'de> + serde::Serialize + Clone>
ShaderCompilation for CachedCompilation<T>
impl<T: ShaderReflectObject> ShaderReflectObject for CachedCompilation<T> {
type Compiler = T::Compiler;
}
impl<T: ShaderReflectObject + for<'de> serde::Deserialize<'de> + serde::Serialize + Clone>
ShaderInputCompiler<CachedCompilation<T>> for Glslang
where
Glslang: ShaderInputCompiler<T>,
{
fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError> {
fn compile(source: &ShaderSource) -> Result<CachedCompilation<T>, ShaderCompileError> {
let cache = crate::cache::internal::get_cache();
let Ok(cache) = cache else {
return Ok(CachedCompilation {
compilation: T::compile(source)?
})
compilation: Glslang::compile(source)?,
});
};
let key = {
@ -30,7 +41,9 @@ impl<T: ShaderCompilation + for<'de> serde::Deserialize<'de> + serde::Serialize
};
let compilation = 'cached: {
if let Ok(cached) = crate::cache::internal::get_blob(&cache, "spirv", key.as_bytes()) {
if let Ok(Some(cached)) =
crate::cache::internal::get_blob(&cache, "spirv", key.as_bytes())
{
let decoded =
bincode::serde::decode_from_slice(&cached, bincode::config::standard())
.map(|(compilation, _)| CachedCompilation { compilation })
@ -42,67 +55,84 @@ impl<T: ShaderCompilation + for<'de> serde::Deserialize<'de> + serde::Serialize
}
CachedCompilation {
compilation: T::compile(source)?,
compilation: Glslang::compile(source)?,
}
};
if let Ok(updated) =
bincode::serde::encode_to_vec(&compilation.compilation, bincode::config::standard())
{
let Ok(()) =
crate::cache::internal::set_blob(&cache, "spirv", key.as_bytes(), &updated)
else {
return Ok(compilation);
};
}
Ok(compilation)
}
}
impl FromCompilation<CachedCompilation<GlslangCompilation>> for DXIL {
type Target = <DXIL as FromCompilation<GlslangCompilation>>::Target;
type Options = <DXIL as FromCompilation<GlslangCompilation>>::Options;
type Context = <DXIL as FromCompilation<GlslangCompilation>>::Context;
type Output = <DXIL as FromCompilation<GlslangCompilation>>::Output;
#[cfg(all(target_os = "windows", feature = "d3d"))]
impl<T> FromCompilation<CachedCompilation<SpirvCompilation>, T> for DXIL
where
DXIL: FromCompilation<SpirvCompilation, T>,
{
type Target = <DXIL as FromCompilation<SpirvCompilation, T>>::Target;
type Options = <DXIL as FromCompilation<SpirvCompilation, T>>::Options;
type Context = <DXIL as FromCompilation<SpirvCompilation, T>>::Context;
type Output = <DXIL as FromCompilation<SpirvCompilation, T>>::Output;
fn from_compilation(
compile: CachedCompilation<GlslangCompilation>,
compile: CachedCompilation<SpirvCompilation>,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
DXIL::from_compilation(compile.compilation)
}
}
impl FromCompilation<CachedCompilation<GlslangCompilation>> for HLSL {
type Target = <HLSL as FromCompilation<GlslangCompilation>>::Target;
type Options = <HLSL as FromCompilation<GlslangCompilation>>::Options;
type Context = <HLSL as FromCompilation<GlslangCompilation>>::Context;
type Output = <HLSL as FromCompilation<GlslangCompilation>>::Output;
impl<T> FromCompilation<CachedCompilation<SpirvCompilation>, T> for HLSL
where
HLSL: FromCompilation<SpirvCompilation, T>,
{
type Target = <HLSL as FromCompilation<SpirvCompilation, T>>::Target;
type Options = <HLSL as FromCompilation<SpirvCompilation, T>>::Options;
type Context = <HLSL as FromCompilation<SpirvCompilation, T>>::Context;
type Output = <HLSL as FromCompilation<SpirvCompilation, T>>::Output;
fn from_compilation(
compile: CachedCompilation<GlslangCompilation>,
compile: CachedCompilation<SpirvCompilation>,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
HLSL::from_compilation(compile.compilation)
}
}
impl FromCompilation<CachedCompilation<GlslangCompilation>> for GLSL {
type Target = <GLSL as FromCompilation<GlslangCompilation>>::Target;
type Options = <GLSL as FromCompilation<GlslangCompilation>>::Options;
type Context = <GLSL as FromCompilation<GlslangCompilation>>::Context;
type Output = <GLSL as FromCompilation<GlslangCompilation>>::Output;
impl<T> FromCompilation<CachedCompilation<SpirvCompilation>, T> for GLSL
where
GLSL: FromCompilation<SpirvCompilation, T>,
{
type Target = <GLSL as FromCompilation<SpirvCompilation, T>>::Target;
type Options = <GLSL as FromCompilation<SpirvCompilation, T>>::Options;
type Context = <GLSL as FromCompilation<SpirvCompilation, T>>::Context;
type Output = <GLSL as FromCompilation<SpirvCompilation, T>>::Output;
fn from_compilation(
compile: CachedCompilation<GlslangCompilation>,
compile: CachedCompilation<SpirvCompilation>,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
GLSL::from_compilation(compile.compilation)
}
}
impl FromCompilation<CachedCompilation<GlslangCompilation>> for SPIRV {
type Target = <SPIRV as FromCompilation<GlslangCompilation>>::Target;
type Options = <SPIRV as FromCompilation<GlslangCompilation>>::Options;
type Context = <SPIRV as FromCompilation<GlslangCompilation>>::Context;
type Output = <SPIRV as FromCompilation<GlslangCompilation>>::Output;
impl<T> FromCompilation<CachedCompilation<SpirvCompilation>, T> for SPIRV
where
SPIRV: FromCompilation<SpirvCompilation, T>,
{
type Target = <SPIRV as FromCompilation<SpirvCompilation, T>>::Target;
type Options = <SPIRV as FromCompilation<SpirvCompilation, T>>::Options;
type Context = <SPIRV as FromCompilation<SpirvCompilation, T>>::Context;
type Output = <SPIRV as FromCompilation<SpirvCompilation, T>>::Output;
fn from_compilation(
compile: CachedCompilation<GlslangCompilation>,
compile: CachedCompilation<SpirvCompilation>,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
SPIRV::from_compilation(compile.compilation)
}

View file

@ -2,6 +2,7 @@
//! here because of the orphan rule.
use crate::{CacheKey, Cacheable};
use windows::core::Interface;
impl CacheKey for windows::Win32::Graphics::Direct3D::ID3DBlob {
fn hash_bytes(&self) -> &[u8] {
@ -17,7 +18,9 @@ impl CacheKey for windows::Win32::Graphics::Direct3D::Dxc::IDxcBlob {
impl Cacheable for windows::Win32::Graphics::Direct3D::ID3DBlob {
fn from_bytes(bytes: &[u8]) -> Option<Self> {
let Some(blob) = (unsafe { windows::Win32::Graphics::Direct3D::Fxc::D3DCreateBlob(bytes.len()).ok() }) else {
let Some(blob) =
(unsafe { windows::Win32::Graphics::Direct3D::Fxc::D3DCreateBlob(bytes.len()).ok() })
else {
return None;
};
@ -43,16 +46,23 @@ impl Cacheable for windows::Win32::Graphics::Direct3D::Dxc::IDxcBlob {
fn from_bytes(bytes: &[u8]) -> Option<Self> {
let Some(blob) = (unsafe {
windows::Win32::Graphics::Direct3D::Dxc::DxcCreateInstance(
&windows::Win32::Graphics::Direct3D::Dxc::CLSID_DxcLibrary)
.and_then(|library: windows::Win32::Graphics::Direct3D::Dxc::IDxcUtils| {
library.CreateBlob(bytes.as_ptr().cast(), bytes.len() as u32,
windows::Win32::Graphics::Direct3D::Dxc::DXC_CP(0))
}).ok()
&windows::Win32::Graphics::Direct3D::Dxc::CLSID_DxcLibrary,
)
.and_then(
|library: windows::Win32::Graphics::Direct3D::Dxc::IDxcUtils| {
library.CreateBlob(
bytes.as_ptr().cast(),
bytes.len() as u32,
windows::Win32::Graphics::Direct3D::Dxc::DXC_CP(0),
)
},
)
.ok()
}) else {
return None;
};
Some(blob.into())
Some(blob.cast().ok()?)
}
fn to_bytes(&self) -> Option<Vec<u8>> {

View file

@ -3,7 +3,7 @@ name = "librashader-capi"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.1.1"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -16,25 +16,55 @@ crate-type = [ "cdylib", "staticlib" ]
[features]
default = ["runtime-all" ]
runtime-all = ["runtime-opengl", "runtime-d3d11", "runtime-d3d12", "runtime-vulkan"]
runtime-opengl = ["gl", "librashader/runtime-gl"]
runtime-all = ["runtime-opengl", "runtime-d3d9", "runtime-d3d11", "runtime-d3d12", "runtime-vulkan", "runtime-metal"]
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"]
runtime-vulkan = ["ash", "librashader/runtime-vk"]
runtime-metal = ["__cbindgen_internal_objc", "librashader/runtime-metal"]
reflect-unstable = []
stable = ["librashader/stable"]
docsrs = []
__cbindgen_internal = ["runtime-all"]
# make runtime-metal depend on this, so its automatically implied.
# this will make cbindgen generate __OBJC__ ifdefs for metal functions.
__cbindgen_internal_objc = ["objc2-metal", "objc2"]
[dependencies]
librashader = { path = "../librashader", version = "0.1.1", features = ["internal"] }
thiserror = "1.0.37"
paste = "1.0.9"
gl = { version = "0.14.0", optional = true }
rustc-hash = "1.1.0"
ash = { version = "0.37.2+1.3.238", optional = true }
spirv_cross = { package = "librashader-spirv-cross", version = "0.23" }
rustc-hash = "2.0.0"
sptr = "0.3.2"
glow = { workspace = true, optional = true }
ash = { workspace = true, optional = true }
[dependencies.librashader]
path = "../librashader"
version = "0.5.1"
default-features = false
features = ["reflect", "presets", "preprocess"]
[target.'cfg(windows)'.dependencies.windows]
version = "0.44.0"
workspace = true
optional = true
[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 }
[package.metadata.docs.rs]
targets = ["x86_64-pc-windows-msvc", "x86_64-unknown-linux-gnu"]
features = ["librashader/docsrs"]
targets = [ "x86_64-pc-windows-msvc",
"x86_64-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"aarch64-apple-ios",
"i686-pc-windows-msvc",
"i686-unknown-linux-gnu", ]
features = ["docsrs", "librashader/docsrs"]

View file

@ -34,28 +34,18 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
after_includes = """
#if defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11)
#include <d3d11.h>
#else
typedef void ID3D11Device;
typedef void ID3D11DeviceContext;
typedef void ID3D11RenderTargetView;
typedef void ID3D11ShaderResourceView;
#endif
#if defined(LIBRA_RUNTIME_VULKAN)
#include <vulkan\\vulkan.h>
#else
typedef int32_t VkFormat;
typedef uint64_t VkImage;
typedef void* VkPhysicalDevice;
typedef void* VkInstance;
typedef void* VkCommandBuffer;
#endif
#if defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12)
#include <d3d12.h>
#else
typedef void ID3D12GraphicsCommandList;
typedef void ID3D12Device;
typedef void ID3D12Resource;
typedef void D3D12_CPU_DESCRIPTOR_HANDLE;
#endif
#if defined(_WIN32) && defined(LIBRA_RUNTIME_D3D9)
#include <D3D9.h>
#endif
#if defined(__APPLE__) && defined(LIBRA_RUNTIME_METAL) && defined(__OBJC__)
#import <Metal/Metal.h>
#endif
#if defined(LIBRA_RUNTIME_VULKAN)
#include <vulkan/vulkan.h>
#endif
"""
@ -64,20 +54,20 @@ typedef void D3D12_CPU_DESCRIPTOR_HANDLE;
"feature = runtime-vulkan" = "LIBRA_RUNTIME_VULKAN"
"feature = runtime-d3d11" = "LIBRA_RUNTIME_D3D11"
"feature = runtime-d3d12" = "LIBRA_RUNTIME_D3D12"
"feature = runtime-d3d9" = "LIBRA_RUNTIME_D3D9"
"feature = runtime-metal" = "LIBRA_RUNTIME_METAL"
"feature = __cbindgen_internal_objc" = "__OBJC__"
"target_os = windows" = "_WIN32"
"target_vendor = apple" = "__APPLE__"
[parse]
parse_deps = true
include = ["librashader",
"librashader-presets",
"librashader-preprocess",
"librashader-reflect",
"librashader-runtime-gl",
"librashader-runtime-vk",
"librashader-runtime-d3d11",
"librashader-runtime-d3d12",
]
expand = ["librashader-capi"]
parse_deps = false
include = ["librashader"]
[parse.expand]
crates = ["librashader-capi"]
features = ["__cbindgen_internal"]
[struct]
@ -99,6 +89,20 @@ include = [
"PFN_libra_preset_print",
"PFN_libra_preset_get_runtime_params",
"PFN_libra_preset_free_runtime_params",
"PFN_libra_preset_create_with_context",
"PFN_libra_preset_ctx_create",
"PFN_libra_preset_ctx_free",
"PFN_libra_preset_ctx_set_core_name",
"PFN_libra_preset_ctx_set_content_dir",
"PFN_libra_preset_ctx_set_param",
"PFN_libra_preset_ctx_set_core_rotation",
"PFN_libra_preset_ctx_set_user_rotation",
"PFN_libra_preset_ctx_set_screen_orientation",
"PFN_libra_preset_ctx_set_allow_rotation",
"PFN_libra_preset_ctx_set_view_aspect_orientation",
"PFN_libra_preset_ctx_set_core_aspect_orientation",
"PFN_libra_preset_ctx_set_runtime",
# error
"PFN_libra_error_errno",
@ -137,6 +141,14 @@ include = [
"PFN_libra_d3d11_filter_chain_get_active_pass_count",
"PFN_libra_d3d11_filter_chain_free",
# d3d11
"PFN_libra_d3d9_filter_chain_create",
"PFN_libra_d3d9_filter_chain_frame",
"PFN_libra_d3d9_filter_chain_set_param",
"PFN_libra_d3d9_filter_chain_get_param",
"PFN_libra_d3d9_filter_chain_set_active_pass_count",
"PFN_libra_d3d9_filter_chain_get_active_pass_count",
"PFN_libra_d3d9_filter_chain_free",
# d3d12
"PFN_libra_d3d12_filter_chain_create",
@ -147,17 +159,38 @@ include = [
"PFN_libra_d3d12_filter_chain_set_active_pass_count",
"PFN_libra_d3d12_filter_chain_get_active_pass_count",
"PFN_libra_d3d12_filter_chain_free",
# metal
"PFN_libra_mtl_filter_chain_create",
"PFN_libra_mtl_filter_chain_create_deferred",
"PFN_libra_mtl_filter_chain_frame",
"PFN_libra_mtl_filter_chain_set_param",
"PFN_libra_mtl_filter_chain_get_param",
"PFN_libra_mtl_filter_chain_set_active_pass_count",
"PFN_libra_mtl_filter_chain_get_active_pass_count",
"PFN_libra_mtl_filter_chain_free",
]
exclude = ["Option_ID3D11DeviceContext"]
exclude = [
"Option_ID3D11DeviceContext",
"Option_PFN_vkGetInstanceProcAddr",
"PMTLCommandQueue",
"PMTLCommandBuffer",
"PMTLTexture"
]
[export.rename]
"LibrashaderError" = "_libra_error"
"ShaderPreset" = "_shader_preset"
"WildcardContext" = "_preset_ctx"
"FilterChainGL" = "_filter_chain_gl"
"FilterChainVulkan" = "_filter_chain_vk"
"FilterChainD3D11" = "_filter_chain_d3d11"
"FilterChainD3D12" = "_filter_chain_d3d12"
"FilterChainD3D9" = "_filter_chain_d3d9"
"FilterChainMetal" = "_filter_chain_mtl"
# vulkan renames
"PhysicalDevice" = "VkPhysicalDevice"
@ -166,6 +199,7 @@ exclude = ["Option_ID3D11DeviceContext"]
"CommandBuffer" = "VkCommandBuffer"
"Format" = "VkFormat"
"Image" = "VkImage"
"Queue" = "VkQueue"
# hack to get proper pointer indirection for COM pointers
# we don't need one for ID3D11DeviceContext.
@ -174,10 +208,22 @@ exclude = ["Option_ID3D11DeviceContext"]
"ID3D11RenderTargetView" = "ID3D11RenderTargetView *"
"ID3D11ShaderResourceView" = "ID3D11ShaderResourceView *"
# hack to get proper pointer indirection for COM pointers
"IDirect3DDevice9" = "IDirect3DDevice9 *"
"IDirect3DSurface9" = "IDirect3DSurface9 *"
"IDirect3DTexture9" = "IDirect3DTexture9 *"
# 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 *"
"ID3D12GraphicsCommandList" = "ID3D12GraphicsCommandList *"
"PMTLCommandQueue" = "id<MTLCommandQueue>"
"PMTLCommandBuffer" = "id<MTLCommandBuffer>"
"PMTLTexture" = "id<MTLTexture>"

View file

@ -1,5 +1,6 @@
//! Binding types for the librashader C API.
use crate::error::LibrashaderError;
use librashader::presets::context::{Orientation, VideoDriver, WildcardContext};
use librashader::presets::ShaderPreset;
use std::mem::MaybeUninit;
use std::ptr::NonNull;
@ -7,42 +8,157 @@ use std::ptr::NonNull;
/// A handle to a shader preset object.
pub type libra_shader_preset_t = Option<NonNull<ShaderPreset>>;
/// A handle to a preset wildcard context object.
pub type libra_preset_ctx_t = Option<NonNull<WildcardContext>>;
/// A handle to a librashader error object.
pub type libra_error_t = Option<NonNull<LibrashaderError>>;
/// An enum representing orientation for use in preset contexts.
#[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 {
fn from(value: LIBRA_PRESET_CTX_ORIENTATION) -> Self {
match value {
LIBRA_PRESET_CTX_ORIENTATION::Vertical => Orientation::Vertical,
LIBRA_PRESET_CTX_ORIENTATION::Horizontal => Orientation::Horizontal,
}
}
}
/// 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 {
fn from(value: LIBRA_PRESET_CTX_RUNTIME) -> Self {
match value {
LIBRA_PRESET_CTX_RUNTIME::None => VideoDriver::None,
LIBRA_PRESET_CTX_RUNTIME::GlCore => VideoDriver::GlCore,
LIBRA_PRESET_CTX_RUNTIME::Vulkan => VideoDriver::Vulkan,
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,
}
}
}
#[cfg(feature = "runtime-opengl")]
use librashader::runtime::gl::FilterChain as FilterChainGL;
/// A handle to a OpenGL filter chain.
#[cfg(feature = "runtime-opengl")]
#[doc(cfg(feature = "runtime-opengl"))]
pub type libra_gl_filter_chain_t = Option<NonNull<librashader::runtime::gl::capi::FilterChainGL>>;
#[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.
#[cfg(all(target_os = "windows", feature = "runtime-d3d11"))]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
pub type libra_d3d11_filter_chain_t =
Option<NonNull<librashader::runtime::d3d11::capi::FilterChainD3D11>>;
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d11")
))]
use librashader::runtime::d3d11::FilterChain as FilterChainD3D11;
/// A handle to a Direct3D 11 filter chain.
#[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 type libra_d3d11_filter_chain_t = Option<NonNull<FilterChainD3D11>>;
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d12")
))]
use librashader::runtime::d3d12::FilterChain as FilterChainD3D12;
/// A handle to a Direct3D 12 filter chain.
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
pub type libra_d3d12_filter_chain_t =
Option<NonNull<librashader::runtime::d3d12::capi::FilterChainD3D12>>;
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d12")
))]
pub type libra_d3d12_filter_chain_t = Option<NonNull<FilterChainD3D12>>;
/// A handle to a Direct3D 9 filter chain.
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d9")
))]
use librashader::runtime::d3d9::FilterChain as FilterChainD3D9;
/// A handle to a Direct3D 11 filter chain.
#[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 type libra_d3d9_filter_chain_t = Option<NonNull<FilterChainD3D9>>;
#[cfg(feature = "runtime-vulkan")]
use librashader::runtime::vk::FilterChain as FilterChainVulkan;
/// A handle to a Vulkan filter chain.
#[cfg(feature = "runtime-vulkan")]
#[doc(cfg(feature = "runtime-vulkan"))]
pub type libra_vk_filter_chain_t =
Option<NonNull<librashader::runtime::vk::capi::FilterChainVulkan>>;
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-vulkan")))]
pub type libra_vk_filter_chain_t = Option<NonNull<FilterChainVulkan>>;
/// Defines the output viewport for a rendered frame.
#[cfg(all(target_os = "macos", feature = "runtime-metal"))]
use librashader::runtime::mtl::FilterChain as FilterChainMetal;
/// 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(
target_vendor = "apple",
feature = "runtime-metal",
feature = "__cbindgen_internal_objc"
)
))]
pub type libra_mtl_filter_chain_t = Option<NonNull<FilterChainMetal>>;
/// 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,
}
@ -54,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
}
@ -87,3 +259,39 @@ macro_rules! config_struct {
pub(crate) use config_set_field;
pub(crate) use config_struct;
pub(crate) use config_version_set;
#[doc(hidden)]
#[deny(deprecated)]
#[deprecated = "Forward declarations for cbindgen, do not use."]
mod __cbindgen_opaque_forward_declarations {
macro_rules! typedef_struct {
($($(#[$($attrss:tt)*])* $name:ident;)*) => {
$($(#[$($attrss)*])*
#[allow(unused)]
#[doc(hidden)]
#[deny(deprecated)]
#[deprecated]
pub struct $name;
)*
};
}
typedef_struct! {
/// Opaque struct for a preset context.
WildcardContext;
/// Opaque struct for a shader preset.
ShaderPreset;
/// Opaque struct for an OpenGL filter chain.
FilterChainGL;
/// Opaque struct for a Direct3D 11 filter chain.
FilterChainD3D11;
/// Opaque struct for a Direct3D 12 filter chain.
FilterChainD3D12;
/// Opaque struct for a Direct3D 9 filter chain.
FilterChainD3D9;
/// Opaque struct for a Vulkan filter chain.
FilterChainVulkan;
/// Opaque struct for a Metal filter chain.
FilterChainMetal;
}
}

View file

@ -5,54 +5,120 @@ use std::mem::MaybeUninit;
use std::ptr::NonNull;
use thiserror::Error;
/// The error type for librashader.
/// The error type for librashader C API.
#[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"))]
#[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),
/// 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 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,
}
@ -67,7 +133,7 @@ pub type PFN_libra_error_errno = extern "C" fn(error: libra_error_t) -> LIBRA_ER
/// - `error` must be valid and initialized.
pub unsafe extern "C" fn libra_error_errno(error: libra_error_t) -> LIBRA_ERRNO {
let Some(error) = error else {
return LIBRA_ERRNO::UNKNOWN_ERROR
return LIBRA_ERRNO::UNKNOWN_ERROR;
};
unsafe { error.as_ref().get_code() }
@ -82,9 +148,7 @@ pub type PFN_libra_error_print = extern "C" fn(error: libra_error_t) -> i32;
/// ## Safety
/// - `error` must be a valid and initialized instance of `libra_error_t`.
pub unsafe extern "C" fn libra_error_print(error: libra_error_t) -> i32 {
let Some(error) = error else {
return 1
};
let Some(error) = error else { return 1 };
unsafe {
let error = error.as_ref();
println!("{error:?}: {error}");
@ -130,9 +194,7 @@ pub unsafe extern "C" fn libra_error_write(
error: libra_error_t,
out: *mut MaybeUninit<*mut c_char>,
) -> i32 {
let Some(error) = error else {
return 1
};
let Some(error) = error else { return 1 };
if out.is_null() {
return 1;
}
@ -140,7 +202,7 @@ pub unsafe extern "C" fn libra_error_write(
unsafe {
let error = error.as_ref();
let Ok(cstring) = CString::new(format!("{error:?}: {error}")) else {
return 1
return 1;
};
out.write(MaybeUninit::new(cstring.into_raw()))
@ -189,8 +251,13 @@ impl LibrashaderError {
LibrashaderError::D3D11FilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
LibrashaderError::D3D12FilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
#[cfg(all(target_os = "windows", feature = "runtime-d3d9"))]
LibrashaderError::D3D9FilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
#[cfg(feature = "runtime-vulkan")]
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 {
@ -203,13 +270,13 @@ impl LibrashaderError {
}
macro_rules! assert_non_null {
($value:ident) => {
if $value.is_null() || !$value.is_aligned() {
(@EXPORT $value:ident) => {
if $value.is_null() || !$crate::ffi::ptr_is_aligned($value) {
return $crate::error::LibrashaderError::InvalidParameter(stringify!($value)).export();
}
};
(noexport $value:ident) => {
if $value.is_null() || !$value.is_aligned() {
($value:ident) => {
if $value.is_null() || !$crate::ffi::ptr_is_aligned($value) {
return Err($crate::error::LibrashaderError::InvalidParameter(
stringify!($value),
));
@ -220,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,40 @@
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> = (|| $crate::ffi::wrap_ok!({
$body
}))();
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
};
e.export()
}
};
($body:block) => {
{
let result: Result<(), $crate::error::LibrashaderError> = try {
$body
let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
$crate::ffi::ffi_body!(nopanic $body)
}));
result.unwrap_or_else(|e| $crate::error::LibrashaderError::UnknownError(e).export())
}
};
(nopanic |$($ref_capture:ident),*|; mut |$($mut_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!(@EXPORT $ref_capture);)*
$(let $ref_capture = unsafe { &*$ref_capture };)*
$($crate::error::assert_non_null!(@EXPORT $mut_capture);)*
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
$body
}))();
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
@ -13,13 +44,20 @@ macro_rules! ffi_body {
};
(|$($ref_capture:ident),*|; mut |$($mut_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!($ref_capture);)*
$(let $ref_capture = unsafe { &*$ref_capture };)*
$($crate::error::assert_non_null!($mut_capture);)*
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = try {
$body
let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
$crate::ffi::ffi_body!(nopanic |$($ref_capture),*|; mut |$($mut_capture),*| $body)
}));
result.unwrap_or_else(|e| $crate::error::LibrashaderError::UnknownError(e).export())
}
};
(nopanic mut |$($mut_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!(@EXPORT $mut_capture);)*
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
$body
}))();
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
@ -29,11 +67,20 @@ macro_rules! ffi_body {
};
(mut |$($mut_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!($mut_capture);)*
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = try {
$body
let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
$crate::ffi::ffi_body!(nopanic mut |$($mut_capture),*| $body)
}));
result.unwrap_or_else(|e| $crate::error::LibrashaderError::UnknownError(e).export())
}
};
(nopanic |$($ref_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!(@EXPORT $ref_capture);)*
$(let $ref_capture = unsafe { &*$ref_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
$body
}))();
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
@ -43,35 +90,17 @@ macro_rules! ffi_body {
};
(|$($ref_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!($ref_capture);)*
$(let $ref_capture = unsafe { &*$ref_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = try {
$body
};
let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
$crate::ffi::ffi_body!(nopanic |$($ref_capture),*| $body)
}));
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
result.unwrap_or_else(|e| $crate::error::LibrashaderError::UnknownError(e).export())
}
};
e.export()
}
}
}
macro_rules! extern_fn {
($(#[$($attrss:tt)*])* fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) $body:block) => {
::paste::paste! {
/// Function pointer definition for
#[doc = ::std::stringify!($func_name)]
pub type [<PFN_ $func_name>] = unsafe extern "C" fn($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t;
}
#[no_mangle]
$(#[$($attrss)*])*
pub unsafe extern "C" fn $func_name($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t {
$crate::ffi::ffi_body!($body)
}
};
// raw doesn't wrap in ffi_body
($(#[$($attrss:tt)*])* raw fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) $body:block) => {
::paste::paste! {
/// Function pointer definition for
@ -86,6 +115,21 @@ macro_rules! extern_fn {
}
};
// ffi_body but panic-safe
($(#[$($attrss:tt)*])* fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) $body:block) => {
::paste::paste! {
/// Function pointer definition for
#[doc = ::std::stringify!($func_name)]
pub type [<PFN_ $func_name>] = unsafe extern "C" fn($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t;
}
#[no_mangle]
$(#[$($attrss)*])*
pub unsafe extern "C" fn $func_name($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t {
$crate::ffi::ffi_body!($body)
}
};
($(#[$($attrss:tt)*])* fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) |$($ref_capture:ident),*|; mut |$($mut_capture:ident),*| $body:block) => {
::paste::paste! {
/// Function pointer definition for
@ -126,7 +170,83 @@ macro_rules! extern_fn {
$crate::ffi::ffi_body!(|$($ref_capture),*| $body)
}
};
// nopanic variants that are UB if panics
($(#[$($attrss:tt)*])* nopanic fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) $body:block) => {
::paste::paste! {
/// Function pointer definition for
#[doc = ::std::stringify!($func_name)]
pub type [<PFN_ $func_name>] = unsafe extern "C" fn($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t;
}
#[no_mangle]
$(#[$($attrss)*])*
pub unsafe extern "C" fn $func_name($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t {
$crate::ffi::ffi_body!(nopanic $body)
}
};
($(#[$($attrss:tt)*])* nopanic fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) |$($ref_capture:ident),*|; mut |$($mut_capture:ident),*| $body:block) => {
::paste::paste! {
/// Function pointer definition for
#[doc = ::std::stringify!($func_name)]
pub type [<PFN_ $func_name>] = unsafe extern "C" fn($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t;
}
#[no_mangle]
$(#[$($attrss)*])*
pub unsafe extern "C" fn $func_name($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t {
$crate::ffi::ffi_body!(nopanic |$($ref_capture),*|; mut |$($mut_capture),*| $body)
}
};
($(#[$($attrss:tt)*])* nopanic fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) mut |$($mut_capture:ident),*| $body:block) => {
::paste::paste! {
/// Function pointer definition for
#[doc = ::std::stringify!($func_name)]
pub type [<PFN_ $func_name>] = unsafe extern "C" fn($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t;
}
#[no_mangle]
$(#[$($attrss)*])*
pub unsafe extern "C" fn $func_name($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t {
$crate::ffi::ffi_body!(nopanic mut |$($mut_capture),*| $body)
}
};
($(#[$($attrss:tt)*])* nopanic fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) |$($ref_capture:ident),*| $body:block) => {
::paste::paste! {
/// Function pointer definition for
#[doc = ::std::stringify!($func_name)]
pub type [<PFN_ $func_name>] = unsafe extern "C" fn($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t;
}
#[no_mangle]
$(#[$($attrss)*])*
pub unsafe extern "C" fn $func_name($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t {
$crate::ffi::ffi_body!(nopanic |$($ref_capture),*| $body)
}
};
}
pub fn boxed_slice_into_raw_parts<T>(vec: Box<[T]>) -> (*mut T, usize) {
let mut me = ManuallyDrop::new(vec);
(me.as_mut_ptr(), me.len())
}
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)) }
}
pub fn ptr_is_aligned<T: Sized>(ptr: *const T) -> bool {
let align = std::mem::align_of::<T>();
if !align.is_power_of_two() {
panic!("is_aligned_to: align is not a power-of-two");
}
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,11 +1,11 @@
#![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
//! possible by linking against `librashader.h` as well as any static libraries used by `librashader`.
//!
//! ## Usage
//! ⚠ Rust consumers use [librashader](https://docs.rs/librashader/) directly instead. ⚠
//! ⚠ Rust consumers should use [librashader](https://docs.rs/librashader/) directly instead. ⚠
//!
//! The librashader C API is designed to be easy to use and safe. Most objects are only accessible behind an opaque pointer.
//! Every allocated object can be freed with a corresponding `free` function **for that specific object type**.
@ -54,32 +54,36 @@
//!
//! ## Thread safety
//!
//! In general, it is **safe** to create a filter chain instance from a different thread, but drawing filter passes must be
//! synchronized externally. The exception to filter chain creation are in OpenGL, where creating the filter chain instance
//! is safe **if and only if** the thread local OpenGL context is initialized 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`.
//! Except for the metal runtime, it is in general, **safe** to create a filter chain instance from a different thread,
//! but drawing filter passes must be synchronized externally. The exception to filter chain creation are in OpenGL,
//! where creating the filter chain instance is safe **if and only if** the thread local OpenGL context is initialized
//! 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)]
#![feature(pointer_is_aligned)]
#![feature(vec_into_raw_parts)]
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(deprecated)]
extern crate alloc;
pub mod ctypes;
pub mod error;
mod ffi;
pub mod presets;
#[cfg(feature = "reflect")]
#[cfg(feature = "reflect-unstable")]
#[doc(hidden)]
pub mod reflect;
pub mod runtime;
pub mod version;
pub mod wildcard;
pub use version::LIBRASHADER_ABI_VERSION;
pub use version::LIBRASHADER_API_VERSION;

View file

@ -1,5 +1,5 @@
//! librashader preset C API (`libra_preset_*`).
use crate::ctypes::libra_shader_preset_t;
use crate::ctypes::{libra_preset_ctx_t, libra_shader_preset_t};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
use librashader::presets::ShaderPreset;
@ -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.
@ -55,7 +54,6 @@ extern_fn! {
let filename = unsafe { CStr::from_ptr(filename) };
let filename = filename.to_str()?;
println!("loading {filename}");
let preset = ShaderPreset::try_parse(filename)?;
unsafe {
@ -66,6 +64,49 @@ extern_fn! {
}
}
extern_fn! {
/// Load a preset with the given wildcard context.
///
/// The wildcard context is immediately invalidated and must be recreated after
/// the preset is created.
///
/// Path information variables `PRESET_DIR` and `PRESET` will automatically be filled in.
/// ## Safety
/// - `filename` must be either null or a valid, aligned pointer to a string path to the shader preset.
/// - `context` must be either null or a valid, aligned pointer to a initialized `libra_preset_ctx_t`.
/// - `context` is invalidated after this function returns.
/// - `out` must be either null, or an aligned pointer to an uninitialized or invalid `libra_shader_preset_t`.
/// ## Returns
/// - If any parameters are null, `out` is unchanged, and this function returns `LIBRA_ERR_INVALID_PARAMETER`.
fn libra_preset_create_with_context(
filename: *const c_char,
context: *mut libra_preset_ctx_t,
out: *mut MaybeUninit<libra_shader_preset_t>
) {
assert_non_null!(filename);
assert_non_null!(context);
assert_non_null!(out);
let filename = unsafe { CStr::from_ptr(filename) };
let filename = filename.to_str()?;
let mut context = unsafe {
let context_ptr = &mut *context;
let context = context_ptr.take();
Box::from_raw(context.unwrap().as_ptr())
};
context.add_path_defaults(filename);
let preset = ShaderPreset::try_parse_with_context(filename, *context)?;
unsafe {
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
preset,
)))))
}
}
}
extern_fn! {
/// Free the preset.
///
@ -73,7 +114,7 @@ extern_fn! {
/// null.
///
/// ## Safety
/// - `preset` must be a valid and aligned pointer to a shader preset.
/// - `preset` must be a valid and aligned pointer to a `libra_shader_preset_t`.
fn libra_preset_free(preset: *mut libra_shader_preset_t) {
assert_non_null!(preset);
unsafe {
@ -88,7 +129,7 @@ extern_fn! {
/// Set the value of the parameter in the preset.
///
/// ## Safety
/// - `preset` must be null or a valid and aligned pointer to a shader preset.
/// - `preset` must be null or a valid and aligned pointer to a `libra_shader_preset_t`.
/// - `name` must be null or a valid and aligned pointer to a string.
fn libra_preset_set_param(
preset: *mut libra_shader_preset_t,
@ -116,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| {
@ -135,7 +176,7 @@ extern_fn! {
/// Pretty print the shader preset.
///
/// ## Safety
/// - `preset` must be null or a valid and aligned pointer to a shader preset.
/// - `preset` must be null or a valid and aligned pointer to a `libra_shader_preset_t`.
fn libra_preset_print(preset: *mut libra_shader_preset_t) |preset| {
assert_some_ptr!(preset);
println!("{preset:#?}");
@ -146,7 +187,7 @@ extern_fn! {
/// Get a list of runtime parameters.
///
/// ## Safety
/// - `preset` must be null or a valid and aligned pointer to a shader preset.
/// - `preset` must be null or a valid and aligned pointer to a `libra_shader_preset_t`.
/// - `out` must be an aligned pointer to a `libra_preset_parameter_list_t`.
/// - The output struct should be treated as immutable. Mutating any struct fields
/// in the returned struct may at best cause memory leaks, and at worse
@ -154,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);
@ -163,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)))?;
@ -176,12 +217,14 @@ extern_fn! {
step: param.step
})
}
let (parts, len, cap) = values.into_raw_parts();
let values = values.into_boxed_slice();
let (parts, len) = crate::ffi::boxed_slice_into_raw_parts(values);
unsafe {
out.write(MaybeUninit::new(libra_preset_param_list_t {
parameters: parts,
length: len as u64,
_internal_alloc: cap as u64,
}));
}
}
@ -209,9 +252,9 @@ extern_fn! {
/// in undefined behaviour.
fn libra_preset_free_runtime_params(preset: libra_preset_param_list_t) {
unsafe {
let values = Vec::from_raw_parts(preset.parameters.cast_mut(),
preset.length as usize,
preset._internal_alloc as usize);
let values =
crate::ffi::boxed_slice_from_raw_parts(preset.parameters.cast_mut(),
preset.length as usize).into_vec();
for value in values {
let name = CString::from_raw(value.name.cast_mut());

View file

@ -1,13 +1,13 @@
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};
use librashader::{FilterMode, WrapMode};
use librashader::reflect::cross::GlslangCompilation;
use librashader::reflect::helper::image::{Image, UVDirection, RGBA8};
use librashader::reflect::SpirvCompilation;
pub(crate) struct LookupTexture {
wrap_mode: WrapMode,
@ -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,11 +35,12 @@ 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,
SPIRV,
GlslangCompilation,
SpirvCompilation,
error::LibrashaderError,
>(passes, &textures)?;

View file

@ -3,46 +3,21 @@ use crate::ctypes::{
};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
use librashader::runtime::d3d11::{D3D11InputView, D3D11OutputView};
use librashader::runtime::d3d11::{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::Direct3D11::{
ID3D11Device, ID3D11DeviceContext, ID3D11RenderTargetView, ID3D11ShaderResourceView,
};
use librashader::runtime::d3d11::capi::options::FilterChainOptionsD3D11;
use librashader::runtime::d3d11::capi::options::FrameOptionsD3D11;
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::d3d11::error::FilterChainError;
use librashader::runtime::{FilterChainParameters, Size, 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>,
/// The width of the source image.
pub width: u32,
/// The height of the source image.
pub height: u32,
}
impl TryFrom<libra_source_image_d3d11_t> for D3D11InputView {
type Error = LibrashaderError;
fn try_from(value: libra_source_image_d3d11_t) -> Result<Self, Self::Error> {
let handle = value.handle.clone();
Ok(D3D11InputView {
handle: ManuallyDrop::into_inner(handle),
size: Size::new(value.width, value.height),
})
}
}
/// Options for Direct3D 11 filter chain creation.
#[repr(C)]
#[derive(Default, Debug, Clone)]
@ -58,7 +33,7 @@ pub struct filter_chain_d3d11_opt_t {
}
config_struct! {
impl FilterChainOptionsD3D11 => filter_chain_d3d11_opt_t {
impl FilterChainOptions => filter_chain_d3d11_opt_t {
0 => [force_no_mipmaps, disable_cache];
}
}
@ -74,11 +49,18 @@ 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, 3 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
/// The current sub frame. Default is 1.
pub current_subframe: u32,
}
config_struct! {
impl FrameOptionsD3D11 => frame_d3d11_opt_t {
impl FrameOptions => frame_d3d11_opt_t {
0 => [clear_history, frame_direction];
1 => [rotation, total_subframes, current_subframe]
}
}
@ -114,7 +96,7 @@ extern_fn! {
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = librashader::runtime::d3d11::capi::FilterChainD3D11::load_from_preset(
let chain = FilterChain::load_from_preset(
*preset,
&device,
options.as_ref(),
@ -174,7 +156,7 @@ extern_fn! {
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = librashader::runtime::d3d11::capi::FilterChainD3D11::load_from_preset_deferred(
let chain = FilterChain::load_from_preset_deferred(
*preset,
&device,
&device_context,
@ -197,17 +179,33 @@ const _: () = assert!(
extern_fn! {
/// Draw a frame with the given parameters for the given filter chain.
///
/// ## 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.
/// 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
/// function will return an error.
/// - `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_gl_opt_t`
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_d3d11_opt_t`
/// struct.
/// - `out` must not be null.
/// - `image.handle` must not be null.
@ -216,15 +214,15 @@ extern_fn! {
/// the filter chain was created with.
/// - 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.
fn libra_d3d11_filter_chain_frame(
nopanic fn libra_d3d11_filter_chain_frame(
chain: *mut libra_d3d11_filter_chain_t,
// cbindgen can't discover that ID3D11DeviceContext has the niche optimization
// 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| {
@ -242,22 +240,27 @@ extern_fn! {
Some(unsafe { options.read() })
};
let viewport = Viewport {
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: D3D11OutputView {
size: Size::new(viewport.width, viewport.height),
handle: ManuallyDrop::into_inner(out.clone()),
output: out.deref(),
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
};
let options = options.map(FromUninit::from_uninit);
let image = image.try_into()?;
unsafe {
chain.frame(device_context.as_deref(), image, &viewport, frame_count, options.as_ref())?;
chain.frame(device_context.as_deref(), image.deref(), &viewport, frame_count, options.as_ref())?;
}
}
}
@ -273,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))
}
}
}
@ -295,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));
@ -322,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);
}
}
@ -334,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

@ -13,26 +13,53 @@ use windows::Win32::Graphics::Direct3D12::{
};
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT;
use librashader::runtime::d3d12::capi::options::FilterChainOptionsD3D12;
use librashader::runtime::d3d12::capi::options::FrameOptionsD3D12;
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::d3d12::{D3D12InputImage, D3D12OutputView};
use librashader::runtime::d3d12::{
D3D12InputImage, D3D12OutputView, FilterChain, FilterChainOptions, FrameOptions,
};
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,
/// The format of the image.
pub format: DXGI_FORMAT,
/// The width of the source image.
pub width: u32,
/// The height of the source image.
pub height: u32,
/// The resource containing the image.
pub resource: ManuallyDrop<ID3D12Resource>,
}
/// Direct3D 12 parameters for the output image.
@ -42,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.
@ -55,11 +86,18 @@ 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, 3 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
/// The current sub frame. Default is 1.
pub current_subframe: u32,
}
config_struct! {
impl FrameOptionsD3D12 => frame_d3d12_opt_t {
impl FrameOptions => frame_d3d12_opt_t {
0 => [clear_history, frame_direction];
1 => [rotation, total_subframes, current_subframe]
}
}
@ -84,26 +122,11 @@ pub struct filter_chain_d3d12_opt_t {
}
config_struct! {
impl FilterChainOptionsD3D12 => filter_chain_d3d12_opt_t {
impl FilterChainOptions => filter_chain_d3d12_opt_t {
0 => [force_hlsl_pipeline, force_no_mipmaps, disable_cache];
}
}
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,
size: Size::new(value.width, value.height),
format: value.format,
})
}
}
extern_fn! {
/// Create the filter chain given the shader preset.
///
@ -136,7 +159,7 @@ extern_fn! {
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = librashader::runtime::d3d12::capi::FilterChainD3D12::load_from_preset(
let chain = FilterChain::load_from_preset(
*preset,
&device,
options.as_ref(),
@ -188,7 +211,7 @@ extern_fn! {
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = librashader::runtime::d3d12::capi::FilterChainD3D12::load_from_preset_deferred(
let chain = FilterChain::load_from_preset_deferred(
*preset,
&device,
&command_list,
@ -206,33 +229,64 @@ 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.
/// - `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_gl_opt_t`
/// - `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.
fn libra_d3d12_filter_chain_frame(
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| {
@ -251,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())?;
}
@ -276,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))
}
}
}
@ -298,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));
@ -325,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);
}
}
@ -337,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

@ -0,0 +1,285 @@
use crate::ctypes::{
config_struct, libra_d3d9_filter_chain_t, libra_shader_preset_t, libra_viewport_t, FromUninit,
};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
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::d3d9::error::FilterChainError;
use librashader::runtime::{FilterChainParameters, Size, Viewport};
/// Options for Direct3D 11 filter chain creation.
#[repr(C)]
#[derive(Default, Debug, Clone)]
pub struct filter_chain_d3d9_opt_t {
/// The librashader API version.
pub version: LIBRASHADER_API_VERSION,
/// Whether or not to explicitly disable mipmap
/// generation regardless of shader preset settings.
pub force_no_mipmaps: bool,
/// Disable the shader object cache. Shaders will be
/// recompiled rather than loaded from the cache.
pub disable_cache: bool,
}
config_struct! {
impl FilterChainOptions => filter_chain_d3d9_opt_t {
0 => [force_no_mipmaps, disable_cache];
}
}
/// Options for each Direct3D 11 shader frame.
#[repr(C)]
#[derive(Default, Debug, Clone)]
pub struct frame_d3d9_opt_t {
/// The librashader API version.
pub version: LIBRASHADER_API_VERSION,
/// Whether or not to clear the history buffers.
pub clear_history: bool,
/// 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, 3 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
/// The current sub frame. Default is 1.
pub current_subframe: u32,
}
config_struct! {
impl FrameOptions => frame_d3d9_opt_t {
0 => [clear_history, frame_direction];
1 => [rotation, total_subframes, current_subframe]
}
}
extern_fn! {
/// Create the filter chain given the shader preset.
///
/// The shader preset is immediately invalidated and must be recreated after
/// the filter chain is created.
///
/// ## Safety:
/// - `preset` must be either null, or valid and aligned.
/// - `options` must be either null, or valid and aligned.
/// - `device` must not be null.
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
fn libra_d3d9_filter_chain_create(
preset: *mut libra_shader_preset_t,
device: ManuallyDrop<IDirect3DDevice9>,
options: *const MaybeUninit<filter_chain_d3d9_opt_t>,
out: *mut MaybeUninit<libra_d3d9_filter_chain_t>
) {
assert_non_null!(preset);
let preset = unsafe {
let preset_ptr = &mut *preset;
let preset = preset_ptr.take();
Box::from_raw(preset.unwrap().as_ptr())
};
let options = if options.is_null() {
None
} else {
Some(unsafe { options.read() })
};
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = FilterChain::load_from_preset(
*preset,
&device,
options.as_ref(),
)?;
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
chain,
)))))
}
}
}
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`
/// struct.
/// - `out` must not be null.
/// - `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.
nopanic fn libra_d3d9_filter_chain_frame(
chain: *mut libra_d3d9_filter_chain_t,
frame_count: usize,
image: ManuallyDrop<IDirect3DTexture9>,
out: ManuallyDrop<IDirect3DSurface9>,
viewport: *const libra_viewport_t,
mvp: *const f32,
options: *const MaybeUninit<frame_d3d9_opt_t>
) mut |chain| {
assert_some_ptr!(mut chain);
let mvp = if mvp.is_null() {
None
} else {
Some(<&[f32; 16]>::try_from(unsafe { slice::from_raw_parts(mvp, 16) }).unwrap())
};
let options = if options.is_null() {
None
} else {
Some(unsafe { options.read() })
};
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(image.deref(), &viewport, frame_count, options.as_ref())?;
}
}
}
extern_fn! {
/// Sets a parameter for the filter chain.
///
/// If the parameter does not exist, returns an error.
/// ## 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.
fn libra_d3d9_filter_chain_set_param(
chain: *mut libra_d3d9_filter_chain_t,
param_name: *const c_char,
value: f32
) |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.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
}
}
}
}
extern_fn! {
/// Gets a parameter for the filter chain.
///
/// If the parameter does not exist, returns an error.
/// ## 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.
fn libra_d3d9_filter_chain_get_param(
chain: *const libra_d3d9_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) |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.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
};
out.write(MaybeUninit::new(value));
}
}
}
extern_fn! {
/// Sets the number of active passes for this chain.
///
/// ## 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_set_active_pass_count(
chain: *mut libra_d3d9_filter_chain_t,
value: u32
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
}
}
extern_fn! {
/// Gets the number of active passes for this chain.
///
/// ## 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: *const libra_d3d9_filter_chain_t,
out: *mut MaybeUninit<u32>
) |chain| {
assert_some_ptr!(chain);
unsafe {
let value = chain.parameters().passes_enabled();
out.write(MaybeUninit::new(value as u32))
}
}
}
extern_fn! {
/// Free a d3d9 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_d3d9_filter_chain_t`.
fn libra_d3d9_filter_chain_free(chain: *mut libra_d3d9_filter_chain_t) {
assert_non_null!(chain);
unsafe {
let chain_ptr = &mut *chain;
let chain = chain_ptr.take();
drop(Box::from_raw(chain.unwrap().as_ptr()))
};
}
}

View file

@ -0,0 +1,5 @@
//! C API for the librashader D3D9 Runtime (`libra_d3d9_*`).
mod filter_chain;
pub use filter_chain::*;
const _: () = crate::assert_thread_safe::<librashader::runtime::d3d11::FilterChain>();

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::{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::capi::options::FilterChainOptionsGL;
use librashader::runtime::gl::capi::options::FrameOptionsGL;
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,11 +56,18 @@ 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, 3 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
/// The current sub frame. Default is 1.
pub current_subframe: u32,
}
config_struct! {
impl FrameOptionsGL => frame_gl_opt_t {
impl FrameOptions => frame_gl_opt_t {
0 => [clear_history, frame_direction];
1 => [rotation, total_subframes, current_subframe]
}
}
@ -92,32 +91,11 @@ pub struct filter_chain_gl_opt_t {
}
config_struct! {
impl FilterChainOptionsGL => filter_chain_gl_opt_t {
impl FilterChainOptions => filter_chain_gl_opt_t {
0 => [glsl_version, use_dsa, force_no_mipmaps, disable_cache];
}
}
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.
///
@ -130,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>
) {
@ -149,7 +128,11 @@ extern_fn! {
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = librashader::runtime::gl::capi::FilterChainGL::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,
@ -161,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.
@ -172,17 +172,19 @@ extern_fn! {
/// thread at a time may call this function. The thread `libra_gl_filter_chain_frame` is called from
/// must have its thread-local OpenGL context initialized with the same context used to create
/// the filter chain.
fn libra_gl_filter_chain_frame(
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 {
@ -195,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 {
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: &framebuffer,
output: &out,
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
};
unsafe {
@ -220,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))
}
}
}
@ -242,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));
@ -269,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);
}
}
@ -281,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))
}
@ -298,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,16 +1,52 @@
//! 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(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-d3d12")))]
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
#[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;
#[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;
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))
)]
#[cfg(any(
feature = "__cbindgen_internal",
all(
target_vendor = "apple",
feature = "runtime-metal",
feature = "__cbindgen_internal_objc"
)
))]
pub mod mtl;

View file

@ -0,0 +1,345 @@
use crate::ctypes::{
config_struct, libra_mtl_filter_chain_t, libra_shader_preset_t, libra_viewport_t, FromUninit,
};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
use librashader::runtime::mtl::{FilterChain, FilterChainOptions, FrameOptions};
use std::ffi::c_char;
use std::ffi::CStr;
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use std::slice;
use librashader::runtime::FilterChainParameters;
use librashader::runtime::{Size, Viewport};
use objc2::runtime::ProtocolObject;
use objc2_metal::{MTLCommandBuffer, MTLCommandQueue, MTLTexture};
use crate::LIBRASHADER_API_VERSION;
/// An alias to a `id<MTLCommandQueue>` protocol object pointer.
pub type PMTLCommandQueue = *const ProtocolObject<dyn MTLCommandQueue>;
/// An alias to a `id<MTLCommandBuffer>` protocol object pointer.
pub type PMTLCommandBuffer = *const ProtocolObject<dyn MTLCommandBuffer>;
/// An alias to a `id<MTLTexture>` protocol object pointer.
pub type PMTLTexture = *const ProtocolObject<dyn MTLTexture>;
/// Options for each Metal shader frame.
#[repr(C)]
#[derive(Default, Debug, Clone)]
pub struct frame_mtl_opt_t {
/// The librashader API version.
pub version: LIBRASHADER_API_VERSION,
/// Whether or not to clear the history buffers.
pub clear_history: bool,
/// 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, 3 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
/// The current sub frame. Default is 1.
pub current_subframe: u32,
}
config_struct! {
impl FrameOptions => frame_mtl_opt_t {
0 => [clear_history, frame_direction];
1 => [rotation, total_subframes, current_subframe]
}
}
/// Options for filter chain creation.
#[repr(C)]
#[derive(Default, Debug, Clone)]
pub struct filter_chain_mtl_opt_t {
/// The librashader API version.
pub version: LIBRASHADER_API_VERSION,
/// Whether or not to explicitly disable mipmap generation regardless of shader preset settings.
pub force_no_mipmaps: bool,
}
config_struct! {
impl FilterChainOptions => filter_chain_mtl_opt_t {
0 => [force_no_mipmaps];
}
}
extern_fn! {
/// Create the filter chain given the shader preset.
///
/// The shader preset is immediately invalidated and must be recreated after
/// the filter chain is created.
///
/// ## Safety:
/// - `queue` must be valid for the command buffers
/// that `libra_mtl_filter_chain_frame` will write to.
/// - `queue` must be a reference to a `id<MTLCommandQueue>`.
/// - `preset` must be either null, or valid and aligned.
/// - `options` must be either null, or valid and aligned.
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
fn libra_mtl_filter_chain_create(
preset: *mut libra_shader_preset_t,
queue: PMTLCommandQueue,
options: *const MaybeUninit<filter_chain_mtl_opt_t>,
out: *mut MaybeUninit<libra_mtl_filter_chain_t>
) |queue| {
assert_non_null!(preset);
let preset = unsafe {
let preset_ptr = &mut *preset;
let preset = preset_ptr.take();
Box::from_raw(preset.unwrap().as_ptr())
};
let options = if options.is_null() {
None
} else {
Some(unsafe { options.read() })
};
let queue = queue.as_ref();
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = FilterChain::load_from_preset(*preset, queue, options.as_ref())?;
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
chain,
)))))
}
}
}
extern_fn! {
/// Create the filter chain given the shader preset deferring and GPU-side initialization
/// to the caller. This function therefore requires no external synchronization of the device queue.
///
/// The shader preset is immediately invalidated and must be recreated after
/// the filter chain is created.
///
/// ## Safety:
/// - `queue` must be valid for the command buffers
/// that `libra_mtl_filter_chain_frame` will write to.
/// - `queue` must be a reference to a `id<MTLCommandQueue>`.
/// - `command_buffer` must be a valid reference to a `MTLCommandBuffer` that is not already encoding.
/// - `preset` must be either null, or valid and aligned.
/// - `options` must be either null, or valid and aligned.
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
///
/// The provided command buffer must be ready for recording and contain no prior commands.
/// The caller is responsible for ending the command buffer and immediately submitting it to a
/// graphics queue. The command buffer must be completely executed before calling `libra_mtl_filter_chain_frame`.
fn libra_mtl_filter_chain_create_deferred(
preset: *mut libra_shader_preset_t,
queue: PMTLCommandQueue,
command_buffer: PMTLCommandBuffer,
options: *const MaybeUninit<filter_chain_mtl_opt_t>,
out: *mut MaybeUninit<libra_mtl_filter_chain_t>
) |queue, command_buffer| {
assert_non_null!(preset);
let preset = unsafe {
let preset_ptr = &mut *preset;
let preset = preset_ptr.take();
Box::from_raw(preset.unwrap().as_ptr())
};
let options = if options.is_null() {
None
} else {
Some(unsafe { options.read() })
};
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = FilterChain::load_from_preset_deferred(*preset,
queue,
command_buffer,
options.as_ref())?;
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
chain,
)))))
}
}
}
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.
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
/// - `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_mtl_opt_t`
/// struct.
/// - 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_mtl_filter_chain_frame(
chain: *mut libra_mtl_filter_chain_t,
command_buffer: PMTLCommandBuffer,
frame_count: usize,
image: PMTLTexture,
output: PMTLTexture,
viewport: *const libra_viewport_t,
mvp: *const f32,
opt: *const MaybeUninit<frame_mtl_opt_t>
) |command_buffer, image, output|; mut |chain| {
assert_some_ptr!(mut chain);
let mvp = if mvp.is_null() {
None
} else {
Some(<&[f32; 16]>::try_from(unsafe { slice::from_raw_parts(mvp, 16) }).unwrap())
};
let opt = if opt.is_null() {
None
} else {
Some(unsafe { opt.read() })
};
let opt = opt.map(FromUninit::from_uninit);
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())?;
}
}
extern_fn! {
/// Sets a parameter for the filter chain.
///
/// If the parameter does not exist, returns an error.
/// ## 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.
fn libra_mtl_filter_chain_set_param(
chain: *mut libra_mtl_filter_chain_t,
param_name: *const c_char,
value: f32
) |chain| {
assert_some_ptr!(chain);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
if chain.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
}
}
}
}
extern_fn! {
/// Gets a parameter for the filter chain.
///
/// If the parameter does not exist, returns an error.
/// ## 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.
fn libra_mtl_filter_chain_get_param(
chain: *const libra_mtl_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) |chain| {
assert_some_ptr!(chain);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
let Some(value) = chain.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
};
out.write(MaybeUninit::new(value));
}
}
}
extern_fn! {
/// Sets the number of active passes for this chain.
///
/// ## 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_set_active_pass_count(
chain: *mut libra_mtl_filter_chain_t,
value: u32
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
}
}
extern_fn! {
/// Gets the number of active passes for this chain.
///
/// ## 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: *const libra_mtl_filter_chain_t,
out: *mut MaybeUninit<u32>
) |chain| {
assert_some_ptr!(chain);
let value = chain.parameters().passes_enabled();
unsafe {
out.write(MaybeUninit::new(value as u32))
}
}
}
extern_fn! {
/// Free a Metal 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_mtl_filter_chain_t`.
fn libra_mtl_filter_chain_free(
chain: *mut libra_mtl_filter_chain_t
) {
assert_non_null!(chain);
unsafe {
let chain_ptr = &mut *chain;
let chain = chain_ptr.take();
drop(Box::from_raw(chain.unwrap().as_ptr()))
};
}
}

View file

@ -0,0 +1,5 @@
//! C API for the librashader Metal Runtime (`libra_mtl_*`).
mod filter_chain;
pub use filter_chain::*;

View file

@ -3,48 +3,38 @@ use crate::ctypes::{
};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
use librashader::runtime::vk::{VulkanImage, VulkanInstance};
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;
use librashader::runtime::vk::capi::options::FilterChainOptionsVulkan;
use librashader::runtime::vk::capi::options::FrameOptionsVulkan;
use librashader::runtime::FilterChainParameters;
use librashader::runtime::{Size, Viewport};
use crate::LIBRASHADER_API_VERSION;
use ash::vk;
pub use ash::vk::PFN_vkGetInstanceProcAddr;
use ash::vk::Handle;
/// 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);
pub use ash::vk::PFN_vkGetInstanceProcAddr;
/// 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 {
@ -57,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,
@ -73,31 +66,45 @@ 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,
}
}
}
/// Options for each OpenGL shader frame.
/// Options for each Vulkan shader frame.
#[repr(C)]
#[derive(Default, Debug, Clone)]
pub struct frame_vk_opt_t {
/// The librashader API version.
pub version: usize,
pub version: LIBRASHADER_API_VERSION,
/// Whether or not to clear the history buffers.
pub clear_history: bool,
/// 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, 3 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
/// The current sub frame. Default is 1.
pub current_subframe: u32,
}
config_struct! {
impl FrameOptionsVulkan => frame_vk_opt_t {
impl FrameOptions => frame_vk_opt_t {
0 => [clear_history, frame_direction];
1 => [rotation, total_subframes, current_subframe]
}
}
@ -106,22 +113,23 @@ config_struct! {
#[derive(Default, Debug, Clone)]
pub struct filter_chain_vk_opt_t {
/// The librashader API version.
pub version: usize,
pub version: LIBRASHADER_API_VERSION,
/// The number of frames in flight to keep. If zero, defaults to three.
pub frames_in_flight: u32,
/// Whether or not to explicitly disable mipmap generation regardless of shader preset settings.
pub force_no_mipmaps: bool,
/// Use explicit render pass objects It is recommended if possible to use dynamic rendering,
/// Use dynamic rendering over explicit render pass objects.
/// It is recommended if possible to use dynamic rendering,
/// because render-pass mode will create new framebuffers per pass.
pub use_render_pass: bool,
pub use_dynamic_rendering: bool,
/// Disable the shader object cache. Shaders will be
/// recompiled rather than loaded from the cache.
pub disable_cache: bool,
}
config_struct! {
impl FilterChainOptionsVulkan => filter_chain_vk_opt_t {
0 => [frames_in_flight, force_no_mipmaps, use_render_pass, disable_cache];
impl FilterChainOptions => filter_chain_vk_opt_t {
0 => [frames_in_flight, force_no_mipmaps, use_dynamic_rendering, disable_cache];
}
}
@ -133,7 +141,7 @@ extern_fn! {
///
/// ## Safety:
/// - The handles provided in `vulkan` must be valid for the command buffers that
/// `libra_vk_filter_chain_frame` will write to. Namely, the VkDevice must have been
/// `libra_vk_filter_chain_frame` will write to.
/// created with the `VK_KHR_dynamic_rendering` extension.
/// - `preset` must be either null, or valid and aligned.
/// - `options` must be either null, or valid and aligned.
@ -161,7 +169,7 @@ extern_fn! {
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = librashader::runtime::vk::capi::FilterChainVulkan::load_from_preset(*preset, vulkan, options.as_ref())?;
let chain = FilterChain::load_from_preset(*preset, vulkan, options.as_ref())?;
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
chain,
@ -179,8 +187,7 @@ extern_fn! {
///
/// ## Safety:
/// - The handles provided in `vulkan` must be valid for the command buffers that
/// `libra_vk_filter_chain_frame` will write to. Namely, the VkDevice must have been
/// created with the `VK_KHR_dynamic_rendering` extension.
/// `libra_vk_filter_chain_frame` will write to.
/// - `preset` must be either null, or valid and aligned.
/// - `options` must be either null, or valid and aligned.
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
@ -212,7 +219,7 @@ extern_fn! {
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = librashader::runtime::vk::capi::FilterChainVulkan::load_from_preset_deferred(*preset,
let chain = FilterChain::load_from_preset_deferred(*preset,
vulkan,
command_buffer,
options.as_ref())?;
@ -228,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.
@ -246,13 +271,13 @@ extern_fn! {
/// struct.
/// - 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.
fn libra_vk_filter_chain_frame(
nopanic fn libra_vk_filter_chain_frame(
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| {
@ -260,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() {
@ -274,11 +299,21 @@ extern_fn! {
Some(unsafe { opt.read() })
};
let opt = opt.map(FromUninit::from_uninit);
let viewport = Viewport {
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 {
@ -298,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))
}
}
}
@ -320,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));
@ -347,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);
}
}
@ -359,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

@ -1,4 +1,4 @@
//! C API for the librashader OpenGL Runtime (`libra_vk_*`).
//! C API for the librashader Vulkan Runtime (`libra_vk_*`).
mod filter_chain;
pub use filter_chain::*;

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.
@ -13,7 +14,11 @@ pub type LIBRASHADER_ABI_VERSION = usize;
/// versions must remain backwards compatible.
/// ## API Versions
/// - API version 0: 0.1.0
pub const LIBRASHADER_CURRENT_VERSION: LIBRASHADER_API_VERSION = 0;
/// - API version 1: 0.2.0
/// - Added rotation, total_subframes, current_subframes to frame options
/// - Added preset context API
/// - Added Metal runtime API
pub const LIBRASHADER_CURRENT_VERSION: LIBRASHADER_API_VERSION = 1;
/// The current version of the librashader ABI.
/// Used by the loader to check ABI compatibility.
@ -23,10 +28,17 @@ pub const LIBRASHADER_CURRENT_VERSION: LIBRASHADER_API_VERSION = 0;
/// 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

@ -0,0 +1,265 @@
//! librashader preset wildcard context C API (`libra_preset_ctx_*`).
use crate::ctypes::{libra_preset_ctx_t, LIBRA_PRESET_CTX_ORIENTATION, LIBRA_PRESET_CTX_RUNTIME};
use crate::error::{assert_non_null, assert_some_ptr};
use librashader::presets::context::{
ContextItem, PresetExtension, Rotation, ShaderExtension, WildcardContext,
};
use std::ffi::{c_char, CStr};
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use crate::ffi::extern_fn;
const _: () = crate::assert_thread_safe::<WildcardContext>();
extern_fn! {
/// Create a wildcard context
///
/// The C API does not allow directly setting certain variables
///
/// - `PRESET_DIR` and `PRESET` are inferred on preset creation.
/// - `VID-DRV-SHADER-EXT` and `VID-DRV-PRESET-EXT` are always set to `slang` and `slangp` for librashader.
/// - `VID-FINAL-ROT` is automatically calculated as the sum of `VID-USER-ROT` and `CORE-REQ-ROT` if either are present.
///
/// 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.
///
/// No variables can be removed once added to the context, however subsequent calls to set the same
/// variable will overwrite the expected variable.
/// ## Safety
/// - `out` must be either null, or an aligned pointer to an uninitialized or invalid `libra_preset_ctx_t`.
/// ## Returns
/// - If any parameters are null, `out` is unchanged, and this function returns `LIBRA_ERR_INVALID_PARAMETER`.
fn libra_preset_ctx_create(
out: *mut MaybeUninit<libra_preset_ctx_t>
) {
assert_non_null!(out);
unsafe {
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
WildcardContext::new(),
)))));
}
}
}
extern_fn! {
/// Free the wildcard context.
///
/// If `context` is null, this function does nothing. The resulting value in `context` then becomes
/// null.
///
/// ## Safety
/// - `context` must be a valid and aligned pointer to a `libra_preset_ctx_t`
fn libra_preset_ctx_free(context: *mut libra_preset_ctx_t) {
assert_non_null!(context);
unsafe {
let context_ptr = &mut *context;
let context = context_ptr.take();
drop(Box::from_raw(context.unwrap().as_ptr()));
}
}
}
extern_fn! {
/// Set the core name (`CORE`) variable in the context
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
/// - `name` must be null or a valid and aligned pointer to a string.
fn libra_preset_ctx_set_core_name(
context: *mut libra_preset_ctx_t,
name: *const c_char,
) |name|; mut |context| {
let name = unsafe {
CStr::from_ptr(name)
};
let name = name.to_str()?;
assert_some_ptr!(mut context);
context.append_item(ContextItem::CoreName(String::from(name)));
}
}
extern_fn! {
/// Set the content directory (`CONTENT-DIR`) variable in the context.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
/// - `name` must be null or a valid and aligned pointer to a string.
fn libra_preset_ctx_set_content_dir(
context: *mut libra_preset_ctx_t,
name: *const c_char,
) |name|; mut |context| {
let name = unsafe {
CStr::from_ptr(name)
};
let name = name.to_str()?;
assert_some_ptr!(mut context);
context.append_item(ContextItem::ContentDirectory(String::from(name)));
}
}
extern_fn! {
/// Set a custom string variable in context.
///
/// If the path contains this variable when loading a preset, it will be replaced with the
/// provided contents.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
/// - `name` must be null or a valid and aligned pointer to a string.
/// - `value` must be null or a valid and aligned pointer to a string.
fn libra_preset_ctx_set_param(
context: *mut libra_preset_ctx_t,
name: *const c_char,
value: *const c_char,
) |name, value|; mut |context| {
let name = unsafe {
CStr::from_ptr(name)
};
let name = name.to_str()?;
let value = unsafe {
CStr::from_ptr(value)
};
let value = value.to_str()?;
assert_some_ptr!(mut context);
context.append_item(ContextItem::ExternContext(String::from(name), String::from(value)));
}
}
extern_fn! {
/// Set the graphics runtime (`VID-DRV`) variable in the context.
///
/// Note that librashader only supports the following runtimes.
///
/// - Vulkan
/// - GLCore
/// - Direct3D11
/// - Direct3D12
/// - Metal
/// - Direct3D9 (HLSL)
///
/// This will also set the appropriate video driver extensions.
///
/// For librashader, `VID-DRV-SHADER-EXT` and `VID-DRV-PRESET-EXT` are always `slang` and `slangp`.
/// To override this, use `libra_preset_ctx_set_param`.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
/// - `name` must be null or a valid and aligned pointer to a string.
fn libra_preset_ctx_set_runtime(
context: *mut libra_preset_ctx_t,
value: LIBRA_PRESET_CTX_RUNTIME,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::VideoDriverPresetExtension(
PresetExtension::Slangp,
));
context.append_item(ContextItem::VideoDriverShaderExtension(
ShaderExtension::Slang,
));
context.append_item(ContextItem::VideoDriver(value.into()));
}
}
extern_fn! {
/// Set the core requested rotation (`CORE-REQ-ROT`) variable in the context.
///
/// Rotation is represented by quarter rotations around the unit circle.
/// For example. `0` = 0deg, `1` = 90deg, `2` = 180deg, `3` = 270deg, `4` = 0deg.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
fn libra_preset_ctx_set_core_rotation(
context: *mut libra_preset_ctx_t,
value: u32,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::CoreRequestedRotation(Rotation::from(value)))
}
}
extern_fn! {
/// Set the user rotation (`VID-USER-ROT`) variable in the context.
///
/// Rotation is represented by quarter rotations around the unit circle.
/// For example. `0` = 0deg, `1` = 90deg, `2` = 180deg, `3` = 270deg, `4` = 0deg.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
fn libra_preset_ctx_set_user_rotation(
context: *mut libra_preset_ctx_t,
value: u32,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::UserRotation(Rotation::from(value)))
}
}
extern_fn! {
/// Set the screen orientation (`SCREEN-ORIENT`) variable in the context.
///
/// Orientation is represented by quarter rotations around the unit circle.
/// For example. `0` = 0deg, `1` = 90deg, `2` = 180deg, `3` = 270deg, `4` = 0deg.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
fn libra_preset_ctx_set_screen_orientation(
context: *mut libra_preset_ctx_t,
value: u32,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::ScreenOrientation(Rotation::from(value)))
}
}
extern_fn! {
/// Set whether or not to allow rotation (`VID-ALLOW-CORE-ROT`) variable in the context.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
fn libra_preset_ctx_set_allow_rotation(
context: *mut libra_preset_ctx_t,
value: bool,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::AllowCoreRotation(value.into()))
}
}
extern_fn! {
/// Set the view aspect orientation (`VIEW-ASPECT-ORIENT`) variable in the context.
//////
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
fn libra_preset_ctx_set_view_aspect_orientation(
context: *mut libra_preset_ctx_t,
value: LIBRA_PRESET_CTX_ORIENTATION,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::ViewAspectOrientation(value.into()))
}
}
extern_fn! {
/// Set the core aspect orientation (`CORE-ASPECT-ORIENT`) variable in the context.
//////
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
fn libra_preset_ctx_set_core_aspect_orientation(
context: *mut libra_preset_ctx_t,
value: LIBRA_PRESET_CTX_ORIENTATION,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::CoreAspectOrientation(value.into()))
}
}

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.1.1"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -13,25 +13,47 @@ 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", "objc2-metal"]
serde = ["dep:serde", "serde/derive", "smartstring/serde", "halfbrown/serde"]
[dependencies]
gl = { version = "0.14.0", optional = true }
ash = { version = "0.37.1+1.3.235", 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
version = "0.44.0"
workspace = true
features = [
"Win32_Foundation",
"Win32_Graphics_Dxgi_Common",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D9",
"Win32_Graphics_Direct3D11",
"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
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

@ -0,0 +1,123 @@
use crate::{FilterMode, GetSize, ImageFormat, Size, WrapMode};
use windows::Win32::Graphics::Direct3D9;
//
impl From<ImageFormat> for Direct3D9::D3DFORMAT {
fn from(format: ImageFormat) -> Self {
match format {
ImageFormat::Unknown => Direct3D9::D3DFMT_UNKNOWN,
ImageFormat::R8Unorm => Direct3D9::D3DFMT_R8G8B8,
ImageFormat::R8Uint => Direct3D9::D3DFMT_R8G8B8,
ImageFormat::R8Sint => Direct3D9::D3DFMT_R8G8B8,
ImageFormat::R8G8B8A8Unorm => Direct3D9::D3DFMT_A8R8G8B8,
ImageFormat::R8G8B8A8Uint => Direct3D9::D3DFMT_A8R8G8B8,
ImageFormat::R8G8B8A8Sint => Direct3D9::D3DFMT_A8R8G8B8,
ImageFormat::R8G8B8A8Srgb => Direct3D9::D3DFMT_A8R8G8B8,
ImageFormat::A2B10G10R10UnormPack32 => Direct3D9::D3DFMT_A2B10G10R10,
ImageFormat::A2B10G10R10UintPack32 => Direct3D9::D3DFMT_A2B10G10R10,
ImageFormat::R16Sfloat => Direct3D9::D3DFMT_R16F,
ImageFormat::R16G16Uint => Direct3D9::D3DFMT_G16R16,
ImageFormat::R16G16Sint => Direct3D9::D3DFMT_G16R16,
ImageFormat::R16G16Sfloat => Direct3D9::D3DFMT_G16R16F,
ImageFormat::R16G16B16A16Uint => Direct3D9::D3DFMT_A16B16G16R16,
ImageFormat::R16G16B16A16Sint => Direct3D9::D3DFMT_A16B16G16R16,
ImageFormat::R16G16B16A16Sfloat => Direct3D9::D3DFMT_A16B16G16R16F,
ImageFormat::R32Sfloat => Direct3D9::D3DFMT_R32F,
_ => Direct3D9::D3DFMT_UNKNOWN,
}
}
}
//
impl From<Direct3D9::D3DFORMAT> for ImageFormat {
fn from(format: Direct3D9::D3DFORMAT) -> Self {
match format {
Direct3D9::D3DFMT_R8G8B8 => ImageFormat::R8Unorm,
Direct3D9::D3DFMT_A8R8G8B8 => ImageFormat::R8G8B8A8Unorm,
Direct3D9::D3DFMT_A2B10G10R10 => ImageFormat::A2B10G10R10UnormPack32,
Direct3D9::D3DFMT_R16F => ImageFormat::R16Sfloat,
Direct3D9::D3DFMT_G16R16 => ImageFormat::R16G16Uint,
Direct3D9::D3DFMT_G16R16F => ImageFormat::R16G16Sfloat,
Direct3D9::D3DFMT_A16B16G16R16 => ImageFormat::R16G16B16A16Uint,
Direct3D9::D3DFMT_A16B16G16R16F => ImageFormat::R16G16B16A16Sfloat,
Direct3D9::D3DFMT_R32F => ImageFormat::R32Sfloat,
_ => ImageFormat::Unknown,
}
}
}
impl From<WrapMode> for Direct3D9::D3DTEXTUREADDRESS {
fn from(value: WrapMode) -> Self {
match value {
WrapMode::ClampToBorder => Direct3D9::D3DTADDRESS_BORDER,
WrapMode::ClampToEdge => Direct3D9::D3DTADDRESS_CLAMP,
WrapMode::Repeat => Direct3D9::D3DTADDRESS_WRAP,
WrapMode::MirroredRepeat => Direct3D9::D3DTADDRESS_MIRROR,
}
}
}
impl From<FilterMode> for Direct3D9::D3DTEXTUREFILTER {
fn from(value: FilterMode) -> Self {
match value {
FilterMode::Linear => Direct3D9::D3DFILTER_LINEAR,
FilterMode::Nearest => Direct3D9::D3DFILTER_NEAREST,
}
}
}
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 {
// match (self, mip) {
// (FilterMode::Linear, FilterMode::Linear) => Direct3D9::D3DFILTER_MIPLINEAR,
// (FilterMode::Linear, FilterMode::Nearest) => Direct3D9::D3DFILTER_LINEARMIPNEAREST,
// (FilterMode::Nearest, FilterMode::Linear) => Direct3D9::D3DFILTER_MIPNEAREST,
// _ => Direct3D9::D3DFILTER_MIPNEAREST
// }
// }
// }

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

@ -8,10 +8,18 @@ pub mod gl;
#[cfg(feature = "vulkan")]
pub mod vk;
/// WGPU common conversions.
#[cfg(feature = "wgpu")]
pub mod wgpu;
/// DXGI common conversions.
#[cfg(all(target_os = "windows", feature = "dxgi"))]
pub mod dxgi;
/// Direct3D 9 common conversions.
#[cfg(all(target_os = "windows", feature = "d3d9"))]
pub mod d3d9;
/// Direct3D 11 common conversions.
#[cfg(all(target_os = "windows", feature = "d3d11"))]
pub mod d3d11;
@ -20,15 +28,32 @@ pub mod d3d11;
#[cfg(all(target_os = "windows", feature = "d3d12"))]
pub mod d3d12;
#[cfg(all(target_vendor = "apple", feature = "metal"))]
pub mod metal;
mod viewport;
#[doc(hidden)]
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)]
#[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]
@ -75,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.
@ -113,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]
@ -173,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,
@ -185,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>,
@ -199,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

@ -0,0 +1,8 @@
/// 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>;
/// A string with small string optimizations up to 23 bytes.
pub type ShortString = smartstring::SmartString<smartstring::LazyCompact>;

View file

@ -0,0 +1,122 @@
use crate::{FilterMode, GetSize, ImageFormat, Size, WrapMode};
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLPixelFormat, MTLSamplerAddressMode, MTLSamplerMinMagFilter, MTLSamplerMipFilter, MTLTexture,
MTLViewport,
};
impl From<ImageFormat> for MTLPixelFormat {
fn from(format: ImageFormat) -> Self {
match format {
ImageFormat::Unknown => MTLPixelFormat(0),
ImageFormat::R8Unorm => MTLPixelFormat::R8Unorm,
ImageFormat::R8Uint => MTLPixelFormat::R8Uint,
ImageFormat::R8Sint => MTLPixelFormat::R8Sint,
ImageFormat::R8G8Unorm => MTLPixelFormat::RG8Unorm,
ImageFormat::R8G8Uint => MTLPixelFormat::RG8Uint,
ImageFormat::R8G8Sint => MTLPixelFormat::RG8Sint,
ImageFormat::R8G8B8A8Unorm => MTLPixelFormat::RGBA8Unorm,
ImageFormat::R8G8B8A8Uint => MTLPixelFormat::RGBA8Uint,
ImageFormat::R8G8B8A8Sint => MTLPixelFormat::RGBA8Sint,
ImageFormat::R8G8B8A8Srgb => MTLPixelFormat::RGBA8Unorm_sRGB,
ImageFormat::A2B10G10R10UnormPack32 => MTLPixelFormat::RGB10A2Unorm,
ImageFormat::A2B10G10R10UintPack32 => MTLPixelFormat::RGB10A2Uint,
ImageFormat::R16Uint => MTLPixelFormat::R16Uint,
ImageFormat::R16Sint => MTLPixelFormat::R16Sint,
ImageFormat::R16Sfloat => MTLPixelFormat::R16Float,
ImageFormat::R16G16Uint => MTLPixelFormat::RG16Uint,
ImageFormat::R16G16Sint => MTLPixelFormat::RG16Sint,
ImageFormat::R16G16Sfloat => MTLPixelFormat::RG16Float,
ImageFormat::R16G16B16A16Uint => MTLPixelFormat::RGBA16Uint,
ImageFormat::R16G16B16A16Sint => MTLPixelFormat::RGBA16Sint,
ImageFormat::R16G16B16A16Sfloat => MTLPixelFormat::RGBA16Float,
ImageFormat::R32Uint => MTLPixelFormat::R32Uint,
ImageFormat::R32Sint => MTLPixelFormat::R32Sint,
ImageFormat::R32Sfloat => MTLPixelFormat::R32Float,
ImageFormat::R32G32Uint => MTLPixelFormat::RG32Uint,
ImageFormat::R32G32Sint => MTLPixelFormat::RG32Sint,
ImageFormat::R32G32Sfloat => MTLPixelFormat::RG32Float,
ImageFormat::R32G32B32A32Uint => MTLPixelFormat::RGBA32Uint,
ImageFormat::R32G32B32A32Sint => MTLPixelFormat::RGBA32Sint,
ImageFormat::R32G32B32A32Sfloat => MTLPixelFormat::RGBA32Float,
}
}
}
impl From<MTLViewport> for Size<u32> {
fn from(value: MTLViewport) -> Self {
Size {
width: value.width as u32,
height: value.height as u32,
}
}
}
impl From<Size<u32>> for MTLViewport {
fn from(value: Size<u32>) -> Self {
MTLViewport {
originX: 0.0,
originY: 0.0,
width: value.width as f64,
height: value.height as f64,
znear: -1.0,
zfar: 1.0,
}
}
}
impl From<WrapMode> for MTLSamplerAddressMode {
fn from(value: WrapMode) -> Self {
match value {
WrapMode::ClampToBorder => MTLSamplerAddressMode::ClampToBorderColor,
WrapMode::ClampToEdge => MTLSamplerAddressMode::ClampToEdge,
WrapMode::Repeat => MTLSamplerAddressMode::Repeat,
WrapMode::MirroredRepeat => MTLSamplerAddressMode::MirrorRepeat,
}
}
}
impl From<FilterMode> for MTLSamplerMinMagFilter {
fn from(value: FilterMode) -> Self {
match value {
FilterMode::Linear => MTLSamplerMinMagFilter::Linear,
_ => MTLSamplerMinMagFilter::Nearest,
}
}
}
impl FilterMode {
/// Get the mipmap filtering mode for the given combination.
pub fn mtl_mip(&self, _mip: FilterMode) -> MTLSamplerMipFilter {
match self {
FilterMode::Linear => MTLSamplerMipFilter::Linear,
FilterMode::Nearest => MTLSamplerMipFilter::Nearest,
}
}
}
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,125 @@
use crate::{FilterMode, ImageFormat, Size, WrapMode};
impl From<ImageFormat> for Option<wgpu_types::TextureFormat> {
fn from(format: ImageFormat) -> Self {
match format {
ImageFormat::Unknown => None,
ImageFormat::R8Unorm => Some(wgpu_types::TextureFormat::R8Unorm),
ImageFormat::R8Uint => Some(wgpu_types::TextureFormat::R8Uint),
ImageFormat::R8Sint => Some(wgpu_types::TextureFormat::R8Sint),
ImageFormat::R8G8Unorm => Some(wgpu_types::TextureFormat::Rg8Unorm),
ImageFormat::R8G8Uint => Some(wgpu_types::TextureFormat::Rg8Uint),
ImageFormat::R8G8Sint => Some(wgpu_types::TextureFormat::Rg8Sint),
ImageFormat::R8G8B8A8Unorm => Some(wgpu_types::TextureFormat::Rgba8Unorm),
ImageFormat::R8G8B8A8Uint => Some(wgpu_types::TextureFormat::Rgba8Uint),
ImageFormat::R8G8B8A8Sint => Some(wgpu_types::TextureFormat::Rgba8Sint),
ImageFormat::R8G8B8A8Srgb => Some(wgpu_types::TextureFormat::Rgba8UnormSrgb),
ImageFormat::A2B10G10R10UnormPack32 => Some(wgpu_types::TextureFormat::Rgb10a2Unorm),
ImageFormat::A2B10G10R10UintPack32 => Some(wgpu_types::TextureFormat::Rgb10a2Uint),
ImageFormat::R16Uint => Some(wgpu_types::TextureFormat::R16Uint),
ImageFormat::R16Sint => Some(wgpu_types::TextureFormat::R16Sint),
ImageFormat::R16Sfloat => Some(wgpu_types::TextureFormat::R16Float),
ImageFormat::R16G16Uint => Some(wgpu_types::TextureFormat::Rg16Uint),
ImageFormat::R16G16Sint => Some(wgpu_types::TextureFormat::Rg16Sint),
ImageFormat::R16G16Sfloat => Some(wgpu_types::TextureFormat::Rg16Float),
ImageFormat::R16G16B16A16Uint => Some(wgpu_types::TextureFormat::Rgba16Uint),
ImageFormat::R16G16B16A16Sint => Some(wgpu_types::TextureFormat::Rgba16Sint),
ImageFormat::R16G16B16A16Sfloat => Some(wgpu_types::TextureFormat::Rgba16Float),
ImageFormat::R32Uint => Some(wgpu_types::TextureFormat::R32Uint),
ImageFormat::R32Sint => Some(wgpu_types::TextureFormat::R32Sint),
ImageFormat::R32Sfloat => Some(wgpu_types::TextureFormat::R32Float),
ImageFormat::R32G32Uint => Some(wgpu_types::TextureFormat::Rg32Uint),
ImageFormat::R32G32Sint => Some(wgpu_types::TextureFormat::Rg32Sint),
ImageFormat::R32G32Sfloat => Some(wgpu_types::TextureFormat::Rg32Float),
ImageFormat::R32G32B32A32Uint => Some(wgpu_types::TextureFormat::Rgba32Uint),
ImageFormat::R32G32B32A32Sint => Some(wgpu_types::TextureFormat::Rgba32Sint),
ImageFormat::R32G32B32A32Sfloat => Some(wgpu_types::TextureFormat::Rgba32Float),
}
}
}
impl From<wgpu_types::TextureFormat> for ImageFormat {
fn from(format: wgpu_types::TextureFormat) -> Self {
match format {
wgpu_types::TextureFormat::R8Unorm => ImageFormat::R8Unorm,
wgpu_types::TextureFormat::R8Uint => ImageFormat::R8Uint,
wgpu_types::TextureFormat::R8Sint => ImageFormat::R8Sint,
wgpu_types::TextureFormat::Rg8Unorm => ImageFormat::R8G8Unorm,
wgpu_types::TextureFormat::Rg8Uint => ImageFormat::R8G8Uint,
wgpu_types::TextureFormat::Rg8Sint => ImageFormat::R8G8Sint,
wgpu_types::TextureFormat::Rgba8Unorm => ImageFormat::R8G8B8A8Unorm,
wgpu_types::TextureFormat::Rgba8Uint => ImageFormat::R8G8B8A8Uint,
wgpu_types::TextureFormat::Rgba8Sint => ImageFormat::R8G8B8A8Sint,
wgpu_types::TextureFormat::Rgba8UnormSrgb => ImageFormat::R8G8B8A8Srgb,
wgpu_types::TextureFormat::Rgb10a2Unorm => ImageFormat::A2B10G10R10UnormPack32,
wgpu_types::TextureFormat::Rgb10a2Uint => ImageFormat::A2B10G10R10UintPack32,
wgpu_types::TextureFormat::R16Uint => ImageFormat::R16Uint,
wgpu_types::TextureFormat::R16Sint => ImageFormat::R16Sint,
wgpu_types::TextureFormat::R16Float => ImageFormat::R16Sfloat,
wgpu_types::TextureFormat::Rg16Uint => ImageFormat::R16G16Uint,
wgpu_types::TextureFormat::Rg16Sint => ImageFormat::R16G16Sint,
wgpu_types::TextureFormat::Rg16Float => ImageFormat::R16G16Sfloat,
wgpu_types::TextureFormat::Rgba16Uint => ImageFormat::R16G16B16A16Uint,
wgpu_types::TextureFormat::Rgba16Sint => ImageFormat::R16G16B16A16Sint,
wgpu_types::TextureFormat::Rgba16Float => ImageFormat::R16G16B16A16Sfloat,
wgpu_types::TextureFormat::R32Uint => ImageFormat::R32Uint,
wgpu_types::TextureFormat::R32Sint => ImageFormat::R32Sint,
wgpu_types::TextureFormat::R32Float => ImageFormat::R32Sfloat,
wgpu_types::TextureFormat::Rg32Uint => ImageFormat::R32G32Uint,
wgpu_types::TextureFormat::Rg32Sint => ImageFormat::R32G32Sint,
wgpu_types::TextureFormat::Rg32Float => ImageFormat::R32G32Sfloat,
wgpu_types::TextureFormat::Rgba32Uint => ImageFormat::R32G32B32A32Uint,
wgpu_types::TextureFormat::Rgba32Sint => ImageFormat::R32G32B32A32Sint,
wgpu_types::TextureFormat::Rgba32Float => ImageFormat::R32G32B32A32Sfloat,
_ => ImageFormat::Unknown,
}
}
}
impl From<Option<wgpu_types::TextureFormat>> for ImageFormat {
fn from(format: Option<wgpu_types::TextureFormat>) -> Self {
let Some(format) = format else {
return ImageFormat::Unknown;
};
ImageFormat::from(format)
}
}
impl From<wgpu_types::Extent3d> for Size<u32> {
fn from(value: wgpu_types::Extent3d) -> Self {
Size {
width: value.width,
height: value.height,
}
}
}
impl From<FilterMode> for wgpu_types::FilterMode {
fn from(value: FilterMode) -> Self {
match value {
FilterMode::Linear => wgpu_types::FilterMode::Linear,
FilterMode::Nearest => wgpu_types::FilterMode::Nearest,
}
}
}
impl From<WrapMode> for wgpu_types::AddressMode {
fn from(value: WrapMode) -> Self {
match value {
WrapMode::ClampToBorder => wgpu_types::AddressMode::ClampToBorder,
WrapMode::ClampToEdge => wgpu_types::AddressMode::ClampToEdge,
WrapMode::Repeat => wgpu_types::AddressMode::Repeat,
WrapMode::MirroredRepeat => wgpu_types::AddressMode::MirrorRepeat,
}
}
}
impl From<Size<u32>> for wgpu_types::Extent3d {
fn from(value: Size<u32>) -> Self {
wgpu_types::Extent3d {
width: value.width,
height: value.height,
depth_or_array_layers: 1,
}
}
}

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.1.1"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -14,15 +14,15 @@ description = "RetroArch shaders for all."
[dependencies]
thiserror = "1.0.37"
nom = "7.1.1"
librashader-common = { path = "../librashader-common", version = "0.1.1" }
rustc-hash = "1.1.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]
librashader-presets = "0.1.0-rc.3"
glob = "0.3.1"
rayon = "1.6.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

@ -25,7 +25,7 @@ fn read_file(path: impl AsRef<Path>) -> Result<String, PreprocessError> {
let buf = e.into_bytes();
let decoder = WINDOWS_1252.new_decoder();
let Some(len) = decoder.max_utf8_buffer_length_without_replacement(buf.len()) else {
return Err(PreprocessError::EncodingError(path.to_path_buf()))
return Err(PreprocessError::EncodingError(path.to_path_buf()));
};
let mut latin1_string = String::with_capacity(len);

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,12 +15,13 @@ mod stage;
use crate::include::read_source;
pub use error::*;
use librashader_common::ImageFormat;
use rustc_hash::FxHashMap;
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,
@ -29,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: FxHashMap<String, ShaderParameter>,
pub parameters: FastHashMap<ShortString, ShaderParameter>,
/// The image format the shader expects.
pub format: ImageFormat,
@ -40,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.
@ -58,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(path: impl AsRef<Path>) -> Result<ShaderSource, PreprocessError> {
load_shader_source(path)
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()),
}
}
}
@ -80,9 +85,14 @@ impl SourceOutput for 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 = FxHashMap::from_iter(meta.parameters.into_iter().map(|p| (p.id.clone(), p)));
let parameters = FastHashMap::from_iter(meta.parameters.into_iter().map(|p| (p.id.clone(), p)));
Ok(ShaderSource {
vertex: text.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,
@ -132,4 +142,34 @@ mod test {
step: 0.25
}, parse_parameter_string(r#"#pragma parameter exc "orizontal correction hack (games where players stay at center)" 0.0 -10.0 10.0 0.25"#).unwrap())
}
#[test]
fn parses_parameter_pragma_test() {
assert_eq!(ShaderParameter {
id: "HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR".into(),
description: " Scanline Dir Multiplier".to_string(),
initial: 100.0,
minimum: 25.0,
maximum: 1600.0,
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

@ -36,7 +36,10 @@ pub(crate) fn process_stages(source: &str) -> Result<ShaderOutput, PreprocessErr
continue;
}
if line.starts_with("#pragma name ") || line.starts_with("#pragma format ") {
if line.starts_with("#pragma name ")
|| line.starts_with("#pragma format ")
|| line.starts_with("#pragma parameter ")
{
continue;
}

View file

@ -1,24 +0,0 @@
use glob::glob;
use librashader_preprocess::ShaderSource;
use librashader_presets::ShaderPreset;
use rayon::prelude::*;
#[test]
fn preprocess_all_slang_presets_parsed() {
for entry in glob("../test/slang-shaders/**/*.slangp").unwrap() {
if let Ok(path) = entry {
if let Ok(preset) = ShaderPreset::try_parse(&path) {
preset.shaders.into_par_iter().for_each(|shader| {
if let Err(e) = ShaderSource::load(&shader.name) {
println!(
"Failed to preprocess shader {} from preset {}: {:?}",
shader.name.display(),
path.display(),
e
);
}
})
}
}
}
}

View file

@ -3,7 +3,7 @@ name = "librashader-presets"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.1.1"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -15,11 +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.1.1" }
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"
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

@ -0,0 +1,413 @@
// pub use librashader_presets_context::*;
//! Shader preset wildcard replacement context handling.
//!
//! Implements wildcard replacement of shader paths specified in
//! [RetroArch#15023](https://github.com/libretro/RetroArch/pull/15023).
use librashader_common::map::FastHashMap;
use once_cell::sync::Lazy;
use regex::bytes::Regex;
use std::collections::VecDeque;
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"
}
}
/// The wildcard key associated with the context item.
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()))
}
}
/// Convert the context into a string hashmap.
///
/// This is a one way conversion, and will normalize rotation context items
/// into `VID-FINAL-ROT`.
pub fn into_hashmap(mut self) -> FastHashMap<String, String> {
let mut map = FastHashMap::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: &FastHashMap<String, String>) {
use std::ffi::{OsStr, OsString};
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

@ -25,7 +25,7 @@ pub enum ParsePresetError {
#[error("shader presets must be resolved against an absolute path")]
RootPathWasNotAbsolute,
/// An IO error occurred when reading the shader preset.
#[error("the file was not found during resolution")]
#[error("io error on file {0:?}: {1}")]
IOError(PathBuf, std::io::Error),
/// The shader preset did not contain valid UTF-8 bytes.
#[error("expected utf8 bytes but got invalid utf8")]

View file

@ -3,14 +3,16 @@
//! 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).
#![feature(drain_filter)]
pub mod context;
mod error;
mod parse;
mod preset;
pub use context::WildcardContext;
pub use error::*;
pub use preset::*;

View file

@ -10,6 +10,7 @@ mod value;
pub(crate) type Span<'a> = LocatedSpan<&'a str>;
pub(crate) use token::Token;
use crate::context::{VideoDriver, WildcardContext};
use crate::error::ParsePresetError;
use crate::parse::preset::resolve_values;
use crate::parse::value::parse_preset;
@ -21,8 +22,38 @@ pub(crate) fn remove_if<T>(values: &mut Vec<T>, f: impl FnMut(&T) -> bool) -> Op
impl ShaderPreset {
/// Try to parse the shader preset at the given path.
///
/// This will add path defaults to the wildcard resolution context.
pub fn try_parse(path: impl AsRef<Path>) -> Result<ShaderPreset, ParsePresetError> {
let values = parse_preset(path)?;
let mut context = WildcardContext::new();
context.add_path_defaults(path.as_ref());
let values = parse_preset(path, WildcardContext::new())?;
Ok(resolve_values(values))
}
/// Try to parse the shader preset at the given path.
///
/// This will add path and driver defaults to the wildcard resolution context.
pub fn try_parse_with_driver_context(
path: impl AsRef<Path>,
driver: VideoDriver,
) -> Result<ShaderPreset, ParsePresetError> {
let mut context = WildcardContext::new();
context.add_path_defaults(path.as_ref());
context.add_video_driver_defaults(driver);
let values = parse_preset(path, context)?;
Ok(resolve_values(values))
}
/// Try to parse the shader preset at the given path, with the exact provided context.
///
/// This function does not change any of the values in the provided context, except calculating `VID-FINAL-ROT`
/// if `CORE-REQ-ROT` and `VID-USER-ROT` is present.
pub fn try_parse_with_context(
path: impl AsRef<Path>,
context: WildcardContext,
) -> Result<ShaderPreset, ParsePresetError> {
let values = parse_preset(path, context)?;
Ok(resolve_values(values))
}
}

View file

@ -1,10 +1,13 @@
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
.drain_filter(|f| matches!(*f, Value::Texture { .. }))
let textures: Vec<TextureConfig> =
MakeExtractIf::extract_if(&mut values, |f| matches!(*f, Value::Texture { .. }))
.map(|value| {
if let Value::Texture {
name,
@ -15,22 +18,24 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
} = value
{
TextureConfig {
storage: librashader_common::StorageType::Path(path),
meta: TextureMeta {
name,
path,
wrap_mode,
filter_mode,
mipmap,
},
}
} else {
unreachable!("values should all be of type Texture")
}
})
.collect();
let parameters: Vec<ParameterConfig> = values
.drain_filter(|f| matches!(*f, Value::Parameter { .. }))
let parameters: Vec<ParameterMeta> =
MakeExtractIf::extract_if(&mut values, |f| matches!(*f, Value::Parameter { .. }))
.map(|value| {
if let Value::Parameter(name, value) = value {
ParameterConfig { name, value }
ParameterMeta { name, value }
} else {
unreachable!("values should be all of type parameters")
}
@ -63,8 +68,8 @@ 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
.drain_filter(|v| v.shader_index() == Some(shader))
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),
@ -112,11 +117,12 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
scale_y = scale;
}
let shader = ShaderPassConfig {
let shader = PassConfig {
storage: librashader_common::StorageType::Path(name),
meta: PassMeta {
id,
name,
alias: shader_values.iter().find_map(|f| match f {
Value::Alias(_, value) => Some(value.to_string()),
Value::Alias(_, value) => Some(value.clone()),
_ => None,
}),
filter: shader_values
@ -172,6 +178,7 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
factor: scale_y.unwrap_or_default(),
},
},
},
};
shaders.push(shader)
@ -181,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

@ -3,14 +3,15 @@ use crate::parse::Span;
use nom::branch::alt;
use nom::bytes::complete::{is_not, take_until};
use nom::character::complete::{char, line_ending, multispace1, not_line_ending};
use std::ops::RangeFrom;
use nom::combinator::{eof, map_res, value};
use nom::error::{ErrorKind, ParseError};
use nom::sequence::delimited;
use nom::{
bytes::complete::tag, character::complete::multispace0, IResult, InputIter, InputLength,
InputTake,
bytes::complete::tag, character::complete::multispace0, AsChar, IResult, InputIter,
InputLength, InputTake, Slice,
};
#[derive(Debug)]
@ -52,9 +53,24 @@ fn parse_assignment(input: Span) -> IResult<Span, ()> {
Ok((input, ()))
}
fn unbalanced_quote<I>(input: I) -> IResult<I, ()>
where
I: Slice<RangeFrom<usize>> + InputIter + InputLength,
<I as InputIter>::Item: AsChar,
I: Copy,
{
if let Ok((input, _)) = eof::<_, ()>(input) {
Ok((input, ()))
} else {
let (input, _) = char('"')(input)?;
Ok((input, ()))
}
}
fn extract_from_quotes(input: Span) -> IResult<Span, Span> {
let (input, between) = delimited(char('"'), is_not("\""), char('"'))(input)?;
let (input, _) = whitespace(input)?;
// Allow unbalanced quotes because some presets just leave an open quote.
let (input, between) = delimited(char('"'), is_not("\""), unbalanced_quote)(input)?;
let (input, _) = opt_whitespace(input)?;
let (input, _) = eof(input)?;
Ok((input, between))
}
@ -71,7 +87,7 @@ fn single_comment(i: Span) -> IResult<Span, Span> {
)(i)
}
fn whitespace(i: Span) -> IResult<Span, ()> {
fn opt_whitespace(i: Span) -> IResult<Span, ()> {
value(
(), // Output is thrown away.
multispace0,
@ -108,7 +124,7 @@ fn parse_tokens(mut span: Span) -> IResult<Span, Vec<Token>> {
let mut values = Vec::new();
while !span.is_empty() {
// important to munch whitespace first.
if let Ok((input, _)) = whitespace(span) {
if let Ok((input, _)) = opt_whitespace(span) {
span = input;
}
// handle references before comments because comments can start with #
@ -155,7 +171,7 @@ pub fn do_lex(input: &str) -> Result<Vec<Token>, ParsePresetError> {
#[cfg(test)]
mod test {
use crate::parse::token::{do_lex, single_comment};
use crate::parse::token::single_comment;
#[test]
fn parses_single_line_comment() {
@ -163,549 +179,4 @@ mod test {
single_comment("// Define textures to be used by the different passes\ntetx=n".into());
eprintln!("{parsed:?}")
}
#[test]
fn parses_key_value_line() {
let parsed = do_lex(TEST3);
eprintln!("{parsed:#?}")
}
// todo: fix
const TEST2: &str = r#"
// Color Correction with Dogway's awesome Grade shader
// Grade is after Afterglow so that brightening the black level does not break the afterglow
shader9 = ../../shaders/dogway/hsm-grade.slang
"#;
const TEST: &str = r#"
#reference "../../alt"
shaders = 54
shader0 = ../../shaders/base/add-params-all.slang
alias0 = "CorePass" # hello
shader1 = ../../shaders/hyllian/cubic/hsm-drez-b-spline-x.slang
filter_linear1 = false
scale_type_x1 = absolute
scale_x1 = 640
scale_type_y1 = viewport
scaley0 = 1.0
wrap_mode1 = "clamp_to_edge"
shader2 = ../../shaders/hyllian/cubic/hsm-drez-b-spline-y.slang
filter_linear2 = false
scale_type2 = absolute
scale_x2 = 640
scale_y2 = 480
wrap_mode2 = "clamp_to_edge"
alias2 = "DerezedPass"
shader3 = ../../shaders/base/add-negative-crop-area.slang
filter_linear3 = false
mipmap_input3 = false
srgb_framebuffer3 = true
scale_type3 = source
scale_x3 = 1
scale_y3 = 1
alias3 = "NegativeCropAddedPass"
shader4 = ../../shaders/base/cache-info-all-params.slang
filter_linear4 = false
scale_type4 = source
scale4 = 1.0
alias4 = "InfoCachePass"
shader5 = ../../shaders/base/text-std.slang
filter_linear5 = false
float_framebuffer5 = true
scale_type5 = source
scale5 = 1.0
alias5 = "TextPass"
shader6 = ../../shaders/base/intro.slang
filter_linear6 = false
float_framebuffer6 = true
scale_type6 = source
scale6 = 1.0
alias6 = "IntroPass"
shader7 = ../../shaders/dedither/dedither-gamma-prep-1-before.slang
alias7 = LinearGamma
shader8 = ../../shaders/hyllian/checkerboard-dedither/checkerboard-dedither-pass1.slang
shader9 = ../../shaders/hyllian/checkerboard-dedither/checkerboard-dedither-pass2.slang
shader10 = ../../shaders/hyllian/checkerboard-dedither/checkerboard-dedither-pass3.slang
alias10 = "PreMdaptPass"
// De-Dithering - Mdapt
shader11 = ../../shaders/mdapt/hsm-mdapt-pass0.slang
shader12 = ../../shaders/mdapt/hsm-mdapt-pass1.slang
shader13 = ../../shaders/mdapt/hsm-mdapt-pass2.slang
shader14 = ../../shaders/mdapt/hsm-mdapt-pass3.slang
shader15 = ../../shaders/mdapt/hsm-mdapt-pass4.slang
shader16 = ../../shaders/dedither/dedither-gamma-prep-2-after.slang
shader17 = ../../shaders/ps1dither/hsm-PS1-Undither-BoxBlur.slang
shader18 = ../../shaders/guest/extras/hsm-sharpsmoother.slang
shader19 = ../../shaders/base/stock.slang
alias19 = refpass
shader20 = ../../shaders/scalefx/hsm-scalefx-pass0.slang
filter_linear20 = false
scale_type20 = source
scale20 = 1.0
float_framebuffer20 = true
alias20 = scalefx_pass0
shader21 = ../../shaders/scalefx/hsm-scalefx-pass1.slang
filter_linear21 = false
scale_type21 = source
scale21 = 1.0
float_framebuffer12 = true
shader22 = ../../shaders/scalefx/hsm-scalefx-pass2.slang
filter_linear22 = false
scale_type22 = source
scale22 = 1.0
shader23 = ../../shaders/scalefx/hsm-scalefx-pass3.slang
filter_linear23 = false
scale_type23 = source
scale23 = 1.0
shader24 = ../../shaders/scalefx/hsm-scalefx-pass4.slang
filter_linear24 = false
scale_type24 = source
scale24 = 3
shader25 = ../../shaders/base/stock.slang
alias25 = "PreCRTPass"
shader26 = ../../shaders/guest/hsm-afterglow0.slang
filter_linear26 = true
scale_type26 = source
scale26 = 1.0
alias26 = "AfterglowPass"
shader27 = ../../shaders/guest/hsm-pre-shaders-afterglow.slang
filter_linear27 = true
scale_type27 = source
mipmap_input27 = true
scale27 = 1.0
// Color Correction with Dogway's awesome Grade shader
// Grade is after Afterglow so that brightening the black level does not break the afterglow
shader28 = ../../shaders/dogway/hsm-grade.slang
filter_linear28 = true
scale_type28 = source
scale28 = 1.0
shader29 = ../../shaders/base/stock.slang
alias29 = "PrePass0"
shader30 = ../../shaders/guest/ntsc/hsm-ntsc-pass1.slang
filter_linear30 = false
float_framebuffer30 = true
scale_type_x30 = source
scale_type_y30 = source
scale_x30 = 4.0
scale_y30 = 1.0
frame_count_mod30 = 2
alias30 = NPass1
shader31 = ../../shaders/guest/ntsc/hsm-ntsc-pass2.slang
float_framebuffer31 = true
filter_linear31 = true
scale_type31 = source
scale_x31 = 0.5
scale_y31 = 1.0
shader32 = ../../shaders/guest/ntsc/hsm-ntsc-pass3.slang
filter_linear32 = true
scale_type32 = source
scale_x32 = 1.0
scale_y32 = 1.0
shader33 = ../../shaders/guest/hsm-custom-fast-sharpen.slang
filter_linear33 = true
scale_type33 = source
scale_x33 = 1.0
scale_y33 = 1.0
shader34 = ../../shaders/base/stock.slang
filter_linear34 = true
scale_type34 = source
scale_x34 = 1.0
scale_y34 = 1.0
alias34 = "PrePass"
mipmap_input34 = true
shader35 = ../../shaders/guest/hsm-avg-lum.slang
filter_linear35 = true
scale_type35 = source
scale35 = 1.0
mipmap_input35 = true
alias35 = "AvgLumPass"
// Pass referenced by subsequent blurring passes and crt pass
shader36 = ../../shaders/guest/hsm-interlace-and-linearize.slang
filter_linear36 = true
scale_type36 = source
scale36 = 1.0
float_framebuffer36 = true
alias36 = "LinearizePass"
shader37 = ../../shaders/guest/hsm-crt-guest-advanced-ntsc-pass1.slang
filter_linear37 = true
scale_type_x37 = viewport
scale_x37 = 1.0
scale_type_y37 = source
scale_y37 = 1.0
float_framebuffer37 = true
alias37 = Pass1
shader38 = ../../shaders/guest/hsm-gaussian_horizontal.slang
filter_linear38 = true
scale_type_x38 = absolute
scale_x38 = 640.0
scale_type_y38 = source
scale_y38 = 1.0
float_framebuffer38 = true
shader39 = ../../shaders/guest/hsm-gaussian_vertical.slang
filter_linear39 = true
scale_type_x39 = absolute
scale_x39 = 640.0
scale_type_y39 = absolute
scale_y39 = 480.0
float_framebuffer39 = true
alias39 = GlowPass
shader40 = ../../shaders/guest/hsm-bloom_horizontal.slang
filter_linear40 = true
scale_type_x40 = absolute
scale_x40 = 640.0
scale_type_y40 = absolute
scale_y40 = 480.0
float_framebuffer40 = true
shader41 = ../../shaders/guest/hsm-bloom_vertical.slang
filter_linear41 = true
scale_type_x41 = absolute
scale_x41 = 640.0
scale_type_y41 = absolute
scale_y41 = 480.0
float_framebuffer41 = true
alias41 = BloomPass
shader42 = ../../shaders/guest/hsm-crt-guest-advanced-ntsc-pass2.slang
filter_linear42 = true
float_framebuffer42 = true
scale_type42 = viewport
scale_x42 = 1.0
scale_y42 = 1.0
shader43 = ../../shaders/guest/hsm-deconvergence.slang
filter_linear43 = true
scale_type43 = viewport
scale_x43 = 1.0
scale_y43 = 1.0
shader44 = ../../shaders/base/post-crt-prep-image-layers.slang
alias44 = "MBZ_PostCRTPass"
// Reduce Resolution ----------------------------------------------------------------
// Reduce the resolution to a small static size regardless of final resolution
// Allows consistent look and faster at different final resolutions for blur
// Mipmap option allows downscaling without artifacts
shader45 = ../../shaders/base/linearize-crt.slang
mipmap_input45 = true
filter_linear45 = true
scale_type45 = absolute
// scale_x45 = 480
// scale_y45 = 270
// scale_x45 = 960
// scale_y45 = 540
scale_x45 = 800
scale_y45 = 600
alias45 = "BR_MirrorLowResPass"
// Add Blur for the Reflection (Horizontal) ----------------------------------------------------------------
shader46 = ../../shaders/base/blur-outside-screen-horiz.slang
mipmap_input46 = true
filter_linear46 = true
// Add Blur for the Reflection (Vertical) ----------------------------------------------------------------
shader47 = ../../shaders/base/blur-outside-screen-vert.slang
filter_linear47 = true
alias47 = "BR_MirrorBlurredPass"
// Reduce resolution ----------------------------------------------------------------
// Reduced to a very small amount so we can create a blur which will create a glow from the screen
// Mipmap option allows smoother downscaling
shader48 = ../../../../blurs/shaders/royale/blur9x9.slang
mipmap_input48 = true
filter_linear48 = true
scale_type48 = absolute
scale_x48 = 128
scale_y48 = 128
alias48 = "BR_MirrorReflectionDiffusedPass"
// Add Diffused glow all around the screen ----------------------------------------------------------------
// Blurred so much that it's non directional
// Mipmap option allows downscaling without artifacts
shader49 = ../../../../blurs/shaders/royale/blur9x9.slang
mipmap_input49 = true
filter_linear49 = true
scale_type49 = absolute
scale_x49 = 12
scale_y49 = 12
alias49 = "BR_MirrorFullscreenGlowPass"
// Bezel Reflection ----------------------------------------------------------------
shader50 = ../../shaders/base/reflection.slang
scale_type50 = viewport
float_framebuffer50 = true
alias50 = "BR_CRTAndReflectionPass"
// Bezel Generation & Composite of Image Layers ----------------------------------------------------------------
shader51 = ../../shaders/base/bezel-images-under-crt.slang
filter_linear51 = true
scale_type51 = viewport
float_framebuffer51 = true
alias51 = "BR_LayersUnderCRTPass"
shader52 = ../../shaders/base/bezel-images-over-crt.slang
filter_linear52 = true
scale_type52 = viewport
float_framebuffer52 = true
alias52 = "BR_LayersOverCRTPass"
// Combine Passes ----------------------------------------------------------------
shader53 = ../../shaders/base/combine-passes.slang
scale_type53 = viewport
alias53 = "CombinePass"
// Define textures to be used by the different passes
textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4;IntroImage;ScreenPlacementImage;TubeDiffuseImage;TubeColoredGelImage;TubeShadowImage;TubeStaticReflectionImage;BackgroundImage;BackgroundVertImage;ReflectionMaskImage;FrameTextureImage;CabinetGlassImage;DeviceImage;DeviceVertImage;DeviceLEDImage;DecalImage;NightLightingImage;NightLighting2Image;LEDImage;TopLayerImage;"
SamplerLUT1 = ../../shaders/guest/lut/trinitron-lut.png
SamplerLUT1_linear = true
SamplerLUT2 = ../../shaders/guest/lut/inv-trinitron-lut.png
SamplerLUT2_linear = true
SamplerLUT3 = ../../shaders/guest/lut/nec-lut.png
SamplerLUT3_linear = true
SamplerLUT4 = ../../shaders/guest/lut/ntsc-lut.png
SamplerLUT4_linear = true
IntroImage = ../../shaders/textures/IntroImage_MegaBezelLogo.png
IntroImage_linear = true
IntroImage_mipmap = 1
ScreenPlacementImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
ScreenPlacementImage_linear = false
TubeDiffuseImage = ../../shaders/textures/Tube_Diffuse_2390x1792.png
TubeDiffuseImage_linear = true
TubeDiffuseImage_mipmap = 1
TubeColoredGelImage = ../../shaders/textures/Colored_Gel_Rainbow.png
TubeColoredGelImage_linear = true
TubeColoredGelImage_mipmap = 1
TubeShadowImage = ../../shaders/textures/Tube_Shadow_1600x1200.png
TubeShadowImage_linear = true
TubeShadowImage_mipmap = 1
TubeStaticReflectionImage = ../../shaders/textures/TubeGlassOverlayImageCropped_1440x1080.png
TubeStaticReflectionImage_linear = true
TubeStaticReflectionImage_mipmap = 1
ReflectionMaskImage = ../../shaders/textures/Placeholder_White_16x16.png
ReflectionMaskImage_linear = true
ReflectionMaskImage_mipmap = 1
FrameTextureImage = ../../shaders/textures/FrameTexture_2800x2120.png
FrameTextureImage_linear = true
FrameTextureImage_mipmap = 1
BackgroundImage = ../../shaders/textures/BackgroundImage_Carbon_3840x2160.png
BackgroundImage_linear = true
BackgroundImage_mipmap = 1
BackgroundVertImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
BackgroundVertImage_linear = true
BackgroundVertImage_mipmap = 1
CabinetGlassImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
CabinetGlassImage_linear = true
CabinetGlassImage_mipmap = 1
DeviceImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
DeviceImage_linear = true
DeviceImage_mipmap = 1
DeviceVertImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
DeviceVertImage_linear = true
DeviceVertImage_mipmap = 1
DeviceLEDImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
DeviceLEDImage_linear = true
DeviceLEDImage_mipmap = 1
DecalImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
DecalImage_linear = true
DecalImage_mipmap = 1
NightLightingImage = ../../shaders/textures/NightLightingClose_1920x1080.png
NightLightingImage_linear = true
NightLightingImage_mipmap = 1
NightLighting2Image = ../../shaders/textures/NightLightingFar_1920x1080.png
NightLighting2Image_linear = true
NightLighting2Image_mipmap = 1
LEDImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
LEDImage_linear = true
LEDImage_mipmap = 1
TopLayerImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
TopLayerImage_linear = true
TopLayerImage_mipmap = 1
// Use for matching vanilla GDV-Advanced
// HSM_ASPECT_RATIO_MODE = 6
// HSM_CURVATURE_MODE = 0
// SMOOTH-ADV
HSM_DEDITHER_MODE = 1
HSM_SCALEFX_ON = 1
HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR = 300
HSM_CORE_RES_SAMPLING_MULT_OPPOSITE_DIR = 125
HSM_DOWNSAMPLE_BLUR_SCANLINE_DIR = 0
HSM_DOWNSAMPLE_BLUR_OPPOSITE_DIR = 0
ntsc_scale = 0.4
shadowMask = 3
// NTSC Parameters
GAMMA_INPUT = 2.0
gamma_out = 1.95
// DREZ Parameters
SHARPEN = 0"#;
const TEST3: &str = r#"// DUIMON MEGA BEZEL GRAPHICS AND PRESETS | https://duimon.github.io/Gallery-Guides/ | duimonmb@gmail.com
// SOME RIGHTS RESERVED - RELEASED UNDER CC BY NC ND LICENSE https://creativecommons.org/licenses/by-nc-nd/4.0/deed
// ----------------------------------------------------------------------------------------------------------------
// PRESET START
// ----------------------------------------------------------------------------------------------------------------
// SHADER :: CONNECTOR | Interface to Mega Bezel Presets folders.
// Edit the target file in the following reference to globally define the base preset.
// ----------------------------------------------------------------------------------------------------------------
#reference "../../../zzz_global_params/Base_Shader/ADV_Bezel.slangp"
// SHADER :: CONNECTOR :: LOCAL OVERRIDES | Interface to specific base presets.
// Comment out the top reference line and uncomment the following reference line to locally define the base preset.
// Keep in mind that some of the base presets use Integer Scale and may yield unexpected results. (e.g. Megatron)
//#reference "../../../zzz_global_params/Local_Shader/ADV_06.slangp"
// "ADV_06" matches the default "MBZ__1__ADV__GDV.slangp".
// Replace the "06" with any from the following list.
// 01. SMOOTH-ADV__GDV 08. ADV__GDV-MINI-NTSC
// 02. SMOOTH-ADV__GDV-NTSC 09. ADV__GDV-NTSC
// 03. SMOOTH-ADV__MEGATRON 10. ADV__MEGATRON
// 04. SMOOTH-ADV__MEGATRON-NTSC 11. ADV__MEGATRON-NTSC
// 05. ADV__EASYMODE 12. ADV-RESHADE-FX__GDV
// 06. ADV__GDV 13. ADV-SUPER-XBR__GDV
// 07. ADV__GDV-MINI 14. ADV-SUPER-XBR__GDV-NTSC
// INTRO | Intro animation
// ----------------------------------------------------------------------------------------------------------------
// ON
#reference "../../../zzz_global_params/Intro/on.params"
// ON - No Image
//#reference "../../../zzz_global_params/Intro/on_no_image.params"
// ON - Default Mega Bezel intro
//#reference "../../../zzz_global_params/Intro/on_default.params"
// OFF
//#reference "../../../zzz_global_params/Intro/off.params"
// DEVICE | Screen/Monitor/CRT/TV settings
// ----------------------------------------------------------------------------------------------------------------
// DEVICE :: BASE
#reference "../../../res/bezel/Nintendo_GBA/bezel.params"
// DEVICE :: SCALING
#reference "../../../res/scale/Nintendo_GBA/bezel.params"
// DEVICE :: CRT
#reference "../../../res/crt/Nintendo_GBA/bezel.params"
// IMAGE LAYERS
// ----------------------------------------------------------------------------------------------------------------
#reference "../../../res/layers/Nintendo_GBA/bezel.params"
// HSV :: Hue, Saturation, and Value parameters
// ----------------------------------------------------------------------------------------------------------------
// GRAPHICS OVERRIDES | Overrides for Image layers, scaling, etc
// that are not related to Guest's shader. (Three examples are provided)
// These are intended for [Bezel] versions and the following reference should be left commented out for others.
// ----------------------------------------------------------------------------------------------------------------
// GRAPHICS :: OVERRIDES
//#reference "../../../res/overrides/batocera.params"
//#reference "../../../res/overrides/batocera_nocurve.params"
//#reference "../../../res/overrides/batocera_hud.params"
// GLOBAL GRAPHICS :: OVERRIDES
// The user can edit the "user.params" to globally change the presets.
// These are for the bezel, frame, and other graphic attributes.
// Examples are included in the params file and commented out.
// These are also intended for [Bezel] versions and the following reference should be left commented out for others.
#reference "../../../zzz_global_params/Graphics/user.params"
// The following is restricted to the [Custom-Bezel_002] presets.
// One example is included in the params file and commented out.
//#reference "../../../zzz_global_params/Graphics/user2.params"
// SHADER OVERRIDES | Place *.params references to Guest derivatives here.
// (Make sure you are using ADV__GDV, STD__GDV, or POTATO__GDV base presets for variations on the Guest shader.)
// Two examples were kindly provided by guest.r. ;-)
// ----------------------------------------------------------------------------------------------------------------
// SHADER :: OVERRIDES
//#reference "../../../res/overrides_shader/guest_custom_aperture.params"
//#reference "../../../res/overrides_shader/guest_custom_slotmask.params"
// GLOBAL SHADER :: OVERRIDES
// The user can edit the target params file to globally change the presets.
// To use community params that require another base preset, change the global base reference to match.
// Examples are included in the params file and commented out.
// Separate folders let users change global settings on each of the sets.
// These are intentionally commented out for LCD-GRID presets.
//#reference "../../../zzz_global_params/Shader/ADV/user_Bezel.params"
//#reference "../../../zzz_global_params/Shader/ADV_DREZ/user_Bezel.params"
//#reference "../../../zzz_global_params/Shader/STD/user_Bezel.params"
//#reference "../../../zzz_global_params/Shader/STD_DREZ/user_Bezel.params"
//#reference "../../../zzz_global_params/Shader/LITE/user_Bezel.params"
// AMBIENT LIGHTING
//#reference "../../../res/lighting/night.params"
// PRESET END
// ----------------------------------------------------------------------------------------------------------------
"#;
}

View file

@ -10,16 +10,20 @@ use nom::IResult;
use num_traits::cast::ToPrimitive;
use crate::parse::token::do_lex;
use librashader_common::map::{FastHashMap, ShortString};
use librashader_common::{FilterMode, WrapMode};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use crate::context::{apply_context, WildcardContext};
use vec_extract_if_polyfill::MakeExtractIf;
#[derive(Debug)]
pub enum Value {
ShaderCount(i32),
FeedbackPass(i32),
FeedbackPass(#[allow(unused)] i32),
Shader(i32, PathBuf),
ScaleX(i32, ScaleFactor),
ScaleY(i32, ScaleFactor),
@ -33,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,
@ -148,9 +152,11 @@ fn parse_indexed_key<'a>(key: &'static str, input: Span<'a>) -> IResult<Span<'a>
pub const SHADER_MAX_REFERENCE_DEPTH: usize = 16;
// prereq: root_path must be contextualized
fn load_child_reference_strings(
root_references: Vec<PathBuf>,
root_path: impl AsRef<Path>,
context: &FastHashMap<String, String>,
) -> Result<Vec<(PathBuf, String)>, ParsePresetError> {
let root_path = root_path.as_ref();
@ -159,13 +165,14 @@ fn load_child_reference_strings(
let root_references = vec![(root_path.to_path_buf(), root_references)];
let mut root_references = VecDeque::from(root_references);
// search needs to be depth first to allow for overrides.
while let Some((reference_root, referenced_paths)) = root_references.pop_front() {
while let Some((mut reference_root, referenced_paths)) = root_references.pop_front() {
if reference_depth > SHADER_MAX_REFERENCE_DEPTH {
return Err(ParsePresetError::ExceededReferenceDepth);
}
// enter the current root
reference_depth += 1;
// canonicalize current root
apply_context(&mut reference_root, context);
let reference_root = reference_root
.canonicalize()
.map_err(|e| ParsePresetError::IOError(reference_root.to_path_buf(), e))?;
@ -174,8 +181,10 @@ fn load_child_reference_strings(
// println!("Resolving {referenced_paths:?} against {reference_root:?}.");
for path in referenced_paths {
let mut path = reference_root
.join(path.clone())
let mut path = reference_root.join(path.clone());
apply_context(&mut path, context);
let mut path = path
.canonicalize()
.map_err(|e| ParsePresetError::IOError(path.clone(), e))?;
// println!("Opening {:?}", path);
@ -186,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
.drain_filter(|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();
@ -202,8 +213,16 @@ fn load_child_reference_strings(
Ok(reference_strings.into())
}
pub fn parse_preset(path: impl AsRef<Path>) -> Result<Vec<Value>, ParsePresetError> {
pub(crate) fn parse_preset(
path: impl AsRef<Path>,
context: WildcardContext,
) -> Result<Vec<Value>, ParsePresetError> {
let path = path.as_ref();
let mut path = path.to_path_buf();
let context = context.into_hashmap();
apply_context(&mut path, &context);
let path = path
.canonicalize()
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
@ -214,12 +233,14 @@ pub fn parse_preset(path: impl AsRef<Path>) -> Result<Vec<Value>, ParsePresetErr
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
let tokens = super::token::do_lex(&contents)?;
parse_values(tokens, path)
parse_values(tokens, path, context)
}
// prereq: root_path must be contextualized
pub fn parse_values(
mut tokens: Vec<Token>,
root_path: impl AsRef<Path>,
context: FastHashMap<String, String>,
) -> Result<Vec<Value>, ParsePresetError> {
let mut root_path = root_path.as_ref().to_path_buf();
if root_path.is_relative() {
@ -231,13 +252,15 @@ pub fn parse_values(
root_path.pop();
}
let references: Vec<PathBuf> = tokens
.drain_filter(|token| *token.key.fragment() == "#reference")
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.
let child_strings = load_child_reference_strings(references, &root_path)?;
// the returned references should have context applied
let child_strings = load_child_reference_strings(references, &root_path, &context)?;
let mut all_tokens: Vec<(&Path, Vec<Token>)> = Vec::new();
for (path, string) in child_strings.iter() {
@ -250,13 +273,16 @@ pub fn parse_values(
// load depth first, so all child tokens are first.
// Later tokens take precedence.
all_tokens.push((root_path.as_path(), tokens));
// collect all possible parameter names.
let mut parameter_names: Vec<&str> = Vec::new();
for (_, tokens) in all_tokens.iter_mut() {
for token in tokens.drain_filter(|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());
}
}
}
@ -264,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.drain_filter(|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());
}
}
}
@ -275,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.drain_filter(|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;
@ -306,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.drain_filter(|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()
@ -358,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 {
@ -373,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.
@ -381,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;
@ -462,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) {
@ -525,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,
));
}
@ -536,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()
@ -574,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 {
@ -593,13 +629,14 @@ pub fn parse_values(
#[cfg(test)]
mod test {
use crate::parse::value::parse_preset;
use crate::WildcardContext;
use std::path::PathBuf;
#[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");
let basic = parse_preset(root);
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: PathBuf,
/// 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]
@ -63,10 +78,13 @@ pub enum ScaleType {
Absolute,
/// Scale by the size of the viewport.
Viewport,
/// Scale by the size of the original input quad.
Original,
}
/// 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),
@ -119,6 +137,7 @@ impl FromStr for ScaleType {
"source" => Ok(ScaleType::Input),
"viewport" => Ok(ScaleType::Viewport),
"absolute" => Ok(ScaleType::Absolute),
"original" => Ok(ScaleType::Original),
_ => Err(ParsePresetError::InvalidScaleType(s.to_string())),
}
}
@ -126,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,
@ -135,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,
@ -146,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,
}
@ -173,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
@ -180,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

@ -1,9 +1,10 @@
use glob::glob;
use librashader_presets::context::{ContextItem, VideoDriver, WildcardContext};
use librashader_presets::ShaderPreset;
#[test]
fn parses_all_slang_presets() {
for entry in glob("../test/slang-shaders/**/*.slangp").unwrap() {
for entry in glob("../test/shaders_slang/**/*.slangp").unwrap() {
if let Ok(path) = entry {
if let Err(e) = ShaderPreset::try_parse(&path) {
println!("Could not parse {}: {:?}", path.display(), e)
@ -14,9 +15,20 @@ fn parses_all_slang_presets() {
#[test]
fn parses_problematic() {
for entry in glob("../test/slang-shaders/crt/crt-hyllian-sinc-glow.slangp").unwrap() {
if let Ok(path) = entry {
ShaderPreset::try_parse(&path).expect(&format!("Failed to parse {}", path.display()));
}
}
let path = "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_NDS_DREZ/NDS-[DREZ]-[Native]-[ADV]-[Guest]-[Night].slangp";
ShaderPreset::try_parse(path).expect(&format!("Failed to parse {}", path));
}
#[test]
fn parses_wildcard() {
let path =
"../test/shaders_slang/bezel/Mega_Bezel/resource/wildcard-examples/Preset-01-Core.slangp";
let mut context = WildcardContext::new();
context.add_video_driver_defaults(VideoDriver::Vulkan);
context.append_item(ContextItem::CoreName(String::from("image display")));
ShaderPreset::try_parse_with_context(path, context)
.expect(&format!("Failed to parse {}", path));
}

View file

@ -3,7 +3,7 @@ name = "librashader-reflect"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.1.1"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,28 +12,39 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[dependencies]
shaderc = { version = "0.8.2", features = [] }
glslang = "0.6.0"
bytemuck = "1.13.0"
thiserror = "1.0.37"
bitflags = "1.3.2"
rustc-hash = "1.1.0"
bitflags = "2.4.2"
librashader-common = { path = "../librashader-common", version = "0.1.1" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.1.1" }
librashader-presets = { path = "../librashader-presets", version = "0.1.1" }
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.23", optional = true }
spirv-to-dxil = { version = "0.3", optional = true }
naga = { version = "0.11.0", features = ["glsl-in", "spv-in", "spv-out", "glsl-out", "wgsl-out"], optional = true }
spirv-cross2 = { workspace = true, optional = true }
rspirv = { version = "0.11.0+1.5.4", 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 }
rustc-hash = "2.0.0"
[target.'cfg(windows)'.dependencies.spirv-to-dxil]
version = "0.4.7"
optional = true
[features]
default = ["cross", "serialize"]
unstable-naga = [ "naga", "rspirv" ]
standalone = ["shaderc/build-from-source"]
dxil = ["cross", "spirv-to-dxil"]
cross = [ "spirv_cross", "spirv_cross/glsl", "spirv_cross/hlsl" ]
serialize = [ "serde" ]
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" ]
stable = []
unstable-naga-in = ["naga/glsl-in"]

Binary file not shown.

View file

@ -1,58 +0,0 @@
use crate::back::targets::{GLSL, HLSL};
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::GlslangCompilation;
use crate::reflect::cross::{CompiledProgram, GlslReflect, HlslReflect};
use crate::reflect::ReflectShader;
/// The GLSL version to target.
pub use spirv_cross::glsl::Version as GlslVersion;
/// The HLSL shader model version to target.
pub use spirv_cross::hlsl::ShaderModel as HlslShaderModel;
/// The context for a GLSL compilation via spirv-cross.
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>,
}
impl FromCompilation<GlslangCompilation> for GLSL {
type Target = GLSL;
type Options = GlslVersion;
type Context = CrossGlslContext;
type Output = impl CompileShader<Self::Target, Options = GlslVersion, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: GlslangCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: GlslReflect::try_from(&compile)?,
})
}
}
/// The context for a HLSL compilation via spirv-cross.
pub struct CrossHlslContext {
/// The compiled HLSL program.
pub artifact: CompiledProgram<spirv_cross::hlsl::Target>,
}
impl FromCompilation<GlslangCompilation> 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;
fn from_compilation(
compile: GlslangCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: HlslReflect::try_from(&compile)?,
})
}
}

View file

@ -1,45 +1,66 @@
use crate::back::spirv::WriteSpirV;
use crate::back::{CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput};
use crate::back::targets::{OutputTarget, DXIL};
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;
pub use spirv_to_dxil::DxilObject;
pub use spirv_to_dxil::ShaderModel;
use spirv_to_dxil::{
PushConstantBufferConfig, RuntimeConfig, RuntimeDataBufferConfig, ShaderStage, ValidatorVersion,
};
use crate::back::targets::{OutputTarget, DXIL};
use crate::error::{ShaderCompileError, ShaderReflectError};
use crate::front::GlslangCompilation;
use crate::reflect::cross::GlslReflect;
use crate::reflect::ReflectShader;
impl OutputTarget for DXIL {
type Output = DxilObject;
}
impl FromCompilation<GlslangCompilation> for DXIL {
#[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: GlslangCompilation,
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
let reflect = GlslReflect::try_from(&compile)?;
let vertex = compile.vertex;
let fragment = compile.fragment;
Ok(CompilerBackend {
// we can just reuse WriteSpirV as the backend.
backend: WriteSpirV {
reflect,
vertex,
fragment,
vertex: compile.vertex,
fragment: compile.fragment,
},
})
}
}
#[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 = ();
@ -59,6 +80,7 @@ impl CompileShader<DXIL> for WriteSpirV {
register_space: 0,
base_shader_register: 1,
},
shader_model_max: sm,
..RuntimeConfig::default()
};
@ -68,7 +90,6 @@ impl CompileShader<DXIL> for WriteSpirV {
None,
"main",
ShaderStage::Vertex,
sm,
ValidatorVersion::None,
&config,
)
@ -79,7 +100,6 @@ impl CompileShader<DXIL> for WriteSpirV {
None,
"main",
ShaderStage::Fragment,
sm,
ValidatorVersion::None,
&config,
)
@ -91,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

@ -0,0 +1,50 @@
use crate::back::targets::GLSL;
use crate::back::{CompileReflectShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::cross::{CompiledProgram, SpirvCross};
/// The GLSL version to target.
pub use spirv_cross2::compile::glsl::GlslVersion;
use crate::reflect::cross::glsl::GlslReflect;
/// The context for a GLSL compilation via spirv-cross.
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_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 CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: GlslReflect::try_from(&compile)?,
})
}
}
#[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

@ -0,0 +1,163 @@
use crate::back::targets::HLSL;
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};
/// The HLSL shader model version to target.
pub use spirv_cross2::compile::hlsl::HlslShaderModel;
/// Buffer assignment information
#[derive(Debug, Clone)]
pub struct HlslBufferAssignment {
/// The name of the buffer
pub name: String,
/// The id of the buffer
pub id: u32,
}
/// Buffer assignment information
#[derive(Debug, Clone, Default)]
pub struct HlslBufferAssignments {
/// Buffer assignment information for UBO
pub ubo: Option<HlslBufferAssignment>,
/// Buffer assignment information for Push
pub push: Option<HlslBufferAssignment>,
}
impl HlslBufferAssignments {
fn find_mangled_id(mangled_name: &str) -> Option<u32> {
if !mangled_name.starts_with("_") {
return None;
}
let Some(next_underscore) = mangled_name[1..].find("_") else {
return None;
};
mangled_name[1..next_underscore + 1].parse().ok()
}
fn find_mangled_name(buffer_name: &str, uniform_name: &str, mangled_name: &str) -> bool {
// name prependded
if mangled_name[buffer_name.len()..].starts_with("_")
&& &mangled_name[buffer_name.len() + 1..] == uniform_name
{
return true;
}
false
}
// Check if the mangled name matches.
pub fn contains_uniform(&self, uniform_name: &str, mangled_name: &str) -> bool {
let is_likely_id_mangled = mangled_name.starts_with("_");
if !mangled_name.ends_with(uniform_name) {
return false;
}
if let Some(ubo) = &self.ubo {
if is_likely_id_mangled {
if let Some(id) = Self::find_mangled_id(mangled_name) {
if id == ubo.id {
return true;
}
}
}
// name prependded
if Self::find_mangled_name(&ubo.name, uniform_name, mangled_name) {
return true;
}
}
if let Some(push) = &self.push {
if is_likely_id_mangled {
if let Some(id) = Self::find_mangled_id(mangled_name) {
if id == push.id {
return true;
}
}
}
// name prependded
if Self::find_mangled_name(&push.name, uniform_name, mangled_name) {
return true;
}
}
// Sometimes SPIRV-cross will assign variables to "global"
if Self::find_mangled_name("global", uniform_name, mangled_name) {
return true;
}
false
}
}
/// The context for a HLSL compilation via spirv-cross.
pub struct CrossHlslContext {
/// The compiled HLSL program.
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 CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: HlslReflect::try_from(&compile)?,
})
}
}
#[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;
#[test]
pub fn mangled_id_test() {
assert_eq!(HlslBufferAssignments::find_mangled_id("_19_MVP"), Some(19));
assert_eq!(HlslBufferAssignments::find_mangled_id("_19"), None);
assert_eq!(HlslBufferAssignments::find_mangled_id("_19_"), Some(19));
assert_eq!(HlslBufferAssignments::find_mangled_id("19_"), None);
assert_eq!(HlslBufferAssignments::find_mangled_id("_19MVP"), None);
assert_eq!(
HlslBufferAssignments::find_mangled_id("_19_29_MVP"),
Some(19)
);
}
#[test]
pub fn mangled_name_test() {
assert!(HlslBufferAssignments::find_mangled_name(
"params",
"MVP",
"params_MVP"
));
}
}

View file

@ -1,8 +1,11 @@
pub mod cross;
#[cfg(feature = "dxil")]
#[cfg(all(target_os = "windows", feature = "dxil"))]
pub mod dxil;
mod spirv;
pub mod glsl;
pub mod hlsl;
pub mod msl;
pub mod spirv;
pub mod targets;
pub mod wgsl;
use crate::back::targets::OutputTarget;
use crate::error::{ShaderCompileError, ShaderReflectError};
@ -29,37 +32,50 @@ 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.
pub trait CompileReflectShader<T: OutputTarget, C>:
/// 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,
Options = <T as FromCompilation<C>>::Options,
Context = <T as FromCompilation<C>>::Context,
Options = <T as FromCompilation<C, S>>::Options,
Context = <T as FromCompilation<C, S>>::Context,
> + ReflectShader
where
T: FromCompilation<C>,
T: FromCompilation<C, S>,
{
}
impl<T, C, O> CompileReflectShader<T, C> for O
impl<T, C, O, S> CompileReflectShader<T, C, S> for O
where
T: OutputTarget,
T: FromCompilation<C>,
T: FromCompilation<C, S>,
O: ReflectShader,
O: CompileShader<
T,
Options = <T as FromCompilation<C>>::Options,
Context = <T as FromCompilation<C>>::Context,
Options = <T as FromCompilation<C, S>>::Options,
Context = <T as FromCompilation<C, S>>::Context,
>,
{
}
@ -78,10 +94,23 @@ 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 into an object ready for reflection or compilation.
pub trait FromCompilation<T> {
/// A trait for reflectable compilations that can be transformed
/// into an object ready for reflection or compilation.
///
/// `T` is the compiled reflectable form of the shader.
/// `S` is the semantics under which the shader is reflected.
///
/// librashader currently supports two semantics, [`SpirvCross`](crate::reflect::cross::SpirvCross)
pub trait FromCompilation<T, S> {
/// The target that the transformed object is expected to compile for.
type Target: OutputTarget;
/// Options provided to the compiler.
@ -113,4 +142,56 @@ 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)]
mod test {
use crate::front::{Glslang, ShaderInputCompiler};
use librashader_preprocess::ShaderSource;
pub fn test() {
let result = ShaderSource::load("../test/basic.slang").unwrap();
let _cross = Glslang::compile(&result).unwrap();
}
}

View file

@ -0,0 +1,101 @@
use crate::back::targets::MSL;
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 naga::back::msl::TranslationInfo;
use naga::Module;
/// The MSL language version to target.
pub use spirv_cross2::compile::msl::MslVersion;
/// Compiler options for MSL
#[derive(Debug, Default, Clone)]
pub struct MslNagaCompileOptions {
// pub write_pcb_as_ubo: bool,
pub sampler_bind_group: u32,
}
/// The context for a MSL compilation via spirv-cross.
pub struct CrossMslContext {
/// The compiled HLSL program.
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 CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: MslReflect::try_from(&compile)?,
})
}
}
#[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,
pub module: Module,
}
pub struct NagaMslContext {
pub vertex: NagaMslModule,
pub fragment: NagaMslModule,
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 CompileReflectShader<Self::Target, SpirvCompilation, Naga>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: NagaReflect::try_from(&compile)?,
})
}
}
#[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)?),
})
}
}

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