Compare commits

...

256 commits

Author SHA1 Message Date
Alex Janka 95a9e2c5be update dependencies 2024-08-19 15:10:44 +10:00
Alex Janka 89090757fd fix shaderstorage in tests 2024-08-19 14:53:16 +10:00
Alex Janka 1406ddc5b3 nightly rust-toolchain.toml 2024-08-19 14:53:16 +10:00
Alex Janka bf974ac642 shaders can be either a path or a string 2024-08-19 14:53:16 +10: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
300 changed files with 25144 additions and 4773 deletions

View file

@ -14,34 +14,85 @@ 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
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) }}
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
- name: Build dynamic library
run: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} --target aarch64-unknown-linux-gnu
- name: Upload build artifacts
uses: actions/upload-artifact@v3.1.2
with:
name: ${{ format('librashader-aarch64-ubuntu-{0}-{1}', github.sha, matrix.profile) }}
path: ${{ format('target/aarch64-unknown-linux-gnu/{0}/librashader.*', 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-2024-01-15 # pinned because it seems like there's a segfault on nightly
override: true
targets: aarch64-pc-windows-msvc
- name: Build dynamic library
run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} --target aarch64-pc-windows-msvc
- name: Upload build artifacts
uses: actions/upload-artifact@v3.1.2
with:
name: ${{ format('librashader-aarch64-windows-{0}-{1}', github.sha, matrix.profile) }}
path: ${{ format('target/aarch64-pc-windows-msvc/{0}/librashader.*', matrix.profile) }}

61
.github/workflows/package-obs.yml vendored Normal file
View file

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

83
.github/workflows/pr-full-test.yml vendored Normal file
View file

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

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@v3
- 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

1
.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/

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>

3474
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -10,10 +10,15 @@ members = [
"librashader-runtime-d3d12",
"librashader-runtime-gl",
"librashader-runtime-vk",
"librashader-runtime-mtl",
"librashader-runtime-wgpu",
"librashader-cache",
"librashader-capi",
"librashader-build-script"
]
"librashader-build-script", "librashader-runtime-d3d9"]
resolver = "2"
[workspace.dependencies]
windows = "0.58.0"
[workspace.metadata.release]

115
README.md
View file

@ -6,25 +6,37 @@
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)
![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 experimental
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 &mdash; ⚠️ Experimental Support
wgpu may not support all shaders due to restrictions from WGSL. Direct3D 9 support is experimental and does not fully
support features such as previous frame feedback or history, as well as being unable to support shaders that need Direct3D 10+
only features.
## Usage
@ -33,20 +45,21 @@ 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,11 @@ 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.
### 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,28 +90,8 @@ 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.
```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,
];
```
### Building
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/)
---
For Rust projects, simply add the crate to your `Cargo.toml`.
```
@ -111,6 +107,9 @@ 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.
While librashader has no build-time dependencies, using `librashader_ld.h` may require headers from
the relevant runtime graphics API.
### Writing a librashader Runtime
If you wish to contribute a runtime implementation not already available, see the [librashader-runtime](https://docs.rs/librashader-runtime/latest/librashader_runtime/)
@ -127,9 +126,12 @@ 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)
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
@ -155,14 +157,16 @@ Please report an issue if you run into a shader that works in RetroArch, but not
* 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,7 +175,9 @@ 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.
@ -179,7 +185,7 @@ and are more a heads-up for integrating librashader into your project.
## Versioning
[![Latest Version](https://img.shields.io/crates/v/librashader.svg)](https://crates.io/crates/librashader)
![C ABI](https://img.shields.io/badge/ABI%20version-1-yellowgreen)
![C API](https://img.shields.io/badge/API%20version-0-blue)
![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 +212,20 @@ 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`.
### MSRV Policy
While librashader requires nightly Rust, the following MSRV policy is enforced for unstable library features.
* Windows and macOS: **latest** nightly
* Linux: **1.76**
A CI job runs weekly to ensure librashader continues to build on nightly. Note that the MSRV is only intended to ease distribution on Linux and is allowed to change any time. It generally tracks the latest version of Rust available in the latest version of Ubuntu, but this may change with no warning in a patch release.
## 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 +235,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/).

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,8 +6,8 @@ publish = false
[dependencies]
cbindgen = { git = "https://github.com/eqrion/cbindgen" }
clap = { version = "4.1.0", features = ["derive"] }
cbindgen = "0.26.0"
clap = { version = "=4.1.0", features = ["derive"] }
[package.metadata.release]

View file

@ -2,7 +2,7 @@ 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 +10,16 @@ use std::{env, fs};
struct Args {
#[arg(long, default_value = "debug", global = true)]
profile: String,
#[arg(long, global = true)]
target: Option<String>,
#[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();
@ -32,9 +36,31 @@ pub fn main() {
"--profile={}",
if profile == "debug" { "dev" } else { &profile }
));
Some(cmd.status().expect("Failed to build librashader-capi"));
let output_dir = PathBuf::from(format!("target/{}", profile))
// 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");
}
if let Some(target) = &args.target {
cmd.arg(format!("--target={}", &target));
}
if !args.cargoflags.is_empty() {
cmd.args(args.cargoflags);
}
let status = cmd.status().expect("Failed to build librashader-capi");
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 output_dir = output_dir
.canonicalize()
.expect("Could not find output directory.");
@ -54,7 +80,14 @@ pub fn main() {
.expect("Unable to write bindings.");
println!("Moving artifacts...");
if cfg!(target_os = "linux") {
if cfg!(target_os = "macos") {
let artifacts = &["liblibrashader_capi.dylib", "liblibrashader_capi.a"];
for artifact in artifacts {
let ext = artifact.strip_prefix("lib").unwrap();
let ext = ext.replace("_capi", "");
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap();
}
} 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();
@ -70,11 +103,22 @@ 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", "");
println!("Renaming {artifact} to {ext}");
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap();
}
if output_dir.join("librashader_capi.pdb").exists() {
println!("Renaming librashader_capi.pdb to librashader.pdb");
fs::rename(
output_dir.join("librashader_capi.pdb"),
output_dir.join("librashader.pdb"),
)
.unwrap();
}
}
return 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.3"
version = "0.3.0"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,18 +12,18 @@ description = "RetroArch shaders for all."
[dependencies]
serde = { version = "1.0" }
librashader-reflect = { path = "../librashader-reflect", version = "0.1.3", features = ["serialize", "dxil"] }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.1.3" }
librashader-reflect = { path = "../librashader-reflect", version = "0.3.0", features = ["serialize"] }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.3.0" }
platform-dirs = "0.3.0"
blake3 = { version = "1.3.3" }
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.'cfg(windows)'.dependencies.windows]
version = "0.44.0"
workspace = true
features = [
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D_Fxc",
@ -32,10 +32,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)
)"#,
[],
)?;
tx.commit()?;
Ok(conn)
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(())
},
) {
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);
}
let value = conn.get::<_, ByteVec>(index, &ByteVec::from(key))?.next();
Ok(value.map(|v| v.to_vec()))
}
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),
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())
{
crate::cache::internal::set_blob(&cache, "spirv", key.as_bytes(), &updated)
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.3"
version = "0.3.0"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -16,25 +16,52 @@ crate-type = [ "cdylib", "staticlib" ]
[features]
default = ["runtime-all" ]
runtime-all = ["runtime-opengl", "runtime-d3d11", "runtime-d3d12", "runtime-vulkan"]
runtime-all = ["runtime-opengl", "runtime-d3d9", "runtime-d3d11", "runtime-d3d12", "runtime-vulkan", "runtime-metal"]
runtime-opengl = ["gl", "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 = []
__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.3", 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"
ash = { version = "0.38", optional = true }
spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1" }
sptr = "0.3.2"
[dependencies.librashader]
path = "../librashader"
version = "0.3.0"
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"]
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 = ["librashader/docsrs"]

View file

@ -5,4 +5,4 @@ pub fn main() {
println!("cargo:rustc-link-arg=/DELAYLOAD:dxcompiler.dll");
println!("cargo:rustc-link-arg=/DELAYLOAD:d3d12.dll");
}
}
}

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,37 @@ 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",
"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"
@ -174,10 +206,19 @@ 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 get proper pointer indirection for COM pointers
"ID3D12Device" = "ID3D12Device *"
"ID3D12Resource" = "ID3D12Resource *"
"ID3D12GraphicsCommandList" = "ID3D12GraphicsCommandList *"
"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,31 +8,122 @@ 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 {
Vertical = 0,
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 {
None = 0,
GlCore,
Vulkan,
D3D11,
D3D12,
Metal,
}
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,
}
}
}
#[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>>;
pub type libra_gl_filter_chain_t = Option<NonNull<FilterChainGL>>;
/// A handle to a Direct3D 11 filter chain.
#[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(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")
))]
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.
#[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>>;
pub type libra_vk_filter_chain_t = Option<NonNull<FilterChainVulkan>>;
#[cfg(all(target_os = "macos", feature = "runtime-metal"))]
use librashader::runtime::mtl::FilterChain as FilterChainMetal;
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
#[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 viewport for a rendered frame.
#[repr(C)]
@ -87,3 +179,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,7 +5,7 @@ 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 {
@ -37,10 +37,18 @@ pub enum LibrashaderError {
#[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),
#[cfg(all(target_os = "windows", feature = "runtime-d3d9"))]
#[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),
#[cfg(feature = "runtime-vulkan")]
#[doc(cfg(feature = "runtime-vulkan"))]
#[error("There was an error in the Vulkan filter chain.")]
VulkanFilterError(#[from] librashader::runtime::vk::error::FilterChainError),
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
#[cfg(all(target_vendor = "apple", feature = "runtime-metal"))]
#[error("There was an error in the D3D12 filter chain.")]
MetalFilterError(#[from] librashader::runtime::mtl::error::FilterChainError),
}
/// Error codes for librashader error types.
@ -67,7 +75,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 +90,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 +136,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 +144,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 +193,12 @@ 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,
}
}
pub(crate) const fn ok() -> libra_error_t {
@ -204,12 +212,12 @@ impl LibrashaderError {
macro_rules! assert_non_null {
($value:ident) => {
if $value.is_null() || !$value.is_aligned() {
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() {
if $value.is_null() || !$crate::ffi::ptr_is_aligned($value) {
return Err($crate::error::LibrashaderError::InvalidParameter(
stringify!($value),
));

View file

@ -1,6 +1,31 @@
macro_rules! ffi_body {
(nopanic $body:block) => {
{
let result: Result<(), $crate::error::LibrashaderError> = try {
$body
};
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
};
e.export()
}
};
($body:block) => {
{
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!($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
};
@ -13,8 +38,15 @@ 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 };)*
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!($mut_capture);)*
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = try {
@ -28,20 +60,15 @@ 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)
}));
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
};
e.export()
result.unwrap_or_else(|e| $crate::error::LibrashaderError::UnknownError(e).export())
}
};
(|$($ref_capture:ident),*| $body:block) => {
(nopanic |$($ref_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!($ref_capture);)*
$(let $ref_capture = unsafe { &*$ref_capture };)*
@ -54,24 +81,20 @@ macro_rules! ffi_body {
};
e.export()
}
}
};
(|$($ref_capture:ident),*| $body:block) => {
{
let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
$crate::ffi::ffi_body!(nopanic |$($ref_capture),*| $body)
}));
result.unwrap_or_else(|e| $crate::error::LibrashaderError::UnknownError(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 +109,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 +164,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)) }
}
#[allow(unstable_name_collisions)]
pub fn ptr_is_aligned<T: Sized>(ptr: *const T) -> bool {
use sptr::Strict;
let align = std::mem::align_of::<T>();
if !align.is_power_of_two() {
panic!("is_aligned_to: align is not a power-of-two");
}
ptr.addr() & (align - 1) == 0
}
pub(crate) use extern_fn;
pub(crate) use ffi_body;
use std::mem::ManuallyDrop;

View file

@ -5,7 +5,7 @@
//! 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,34 @@
//!
//! ## 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.
//!
//! 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.
#![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;
@ -55,7 +55,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 +65,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 +115,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 +130,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,
@ -135,7 +177,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 +188,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
@ -176,12 +218,15 @@ 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,
_internal_alloc: 0,
}));
}
}
@ -209,9 +254,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

@ -6,8 +6,8 @@ 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,
@ -38,8 +38,9 @@ impl FilterReflection {
let (passes, textures) = (preset.shaders, preset.textures);
let (passes, semantics) = librashader::reflect::helper::compile_preset_passes::<
Glslang,
SPIRV,
GlslangCompilation,
SpirvCompilation,
error::LibrashaderError,
>(passes, &textures)?;

View file

@ -3,46 +3,31 @@ 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::{FilterChainParameters, Size, Viewport};
use librashader::runtime::{FilterChainParameters, Viewport};
/// Direct3D 11 parameters for the source image.
#[repr(C)]
pub struct libra_source_image_d3d11_t {
/// A shader resource view into the source image
pub handle: ManuallyDrop<ID3D11ShaderResourceView>,
/// The width of the source image.
/// This is currently ignored.
pub width: u32,
/// The height of the source image.
/// This is currently ignored.
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 +43,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 +59,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, 4 = 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 +106,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 +166,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,
@ -207,7 +199,7 @@ extern_fn! {
/// 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,7 +208,7 @@ 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.
@ -245,19 +237,14 @@ extern_fn! {
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output: D3D11OutputView {
size: Size::new(viewport.width, viewport.height),
handle: ManuallyDrop::into_inner(out.clone()),
},
output: ManuallyDrop::into_inner(out.clone()),
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.handle.deref(), &viewport, frame_count, options.as_ref())?;
}
}
}

View file

@ -13,11 +13,10 @@ 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};
/// Direct3D 12 parameters for the source image.
@ -27,11 +26,11 @@ pub struct libra_source_image_d3d12_t {
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.
/// This is currently ignored.
pub format: DXGI_FORMAT,
/// The width of the source image.
/// This is currently ignored.
pub width: u32,
/// The height of the source image.
/// This is currently ignored.
pub height: u32,
}
@ -55,11 +54,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, 4 = 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,7 +90,7 @@ 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];
}
}
@ -98,8 +104,6 @@ impl TryFrom<libra_source_image_d3d12_t> for D3D12InputImage {
Ok(D3D12InputImage {
resource: ManuallyDrop::into_inner(resource),
descriptor: value.descriptor,
size: Size::new(value.width, value.height),
format: value.format,
})
}
}
@ -136,7 +140,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 +192,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,
@ -218,7 +222,7 @@ extern_fn! {
/// 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.
@ -226,7 +230,7 @@ extern_fn! {
/// and must be associated with the `ID3D12Device` this 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_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,

View file

@ -0,0 +1,259 @@
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::ptr::NonNull;
use std::slice;
use windows::Win32::Graphics::Direct3D9::{IDirect3DDevice9, IDirect3DSurface9, IDirect3DTexture9};
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::{FilterChainParameters, 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, 4 = 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.
///
///
/// ## 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_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>,
viewport: libra_viewport_t,
out: ManuallyDrop<IDirect3DSurface9>,
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 = Viewport {
x: viewport.x,
y: viewport.y,
output: ManuallyDrop::into_inner(out.clone()),
mvp,
};
let options = options.map(FromUninit::from_uninit);
unsafe {
chain.frame(ManuallyDrop::into_inner(image.clone()), &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
) mut |chain| {
assert_some_ptr!(mut 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()
}
}
}
}
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: *mut libra_d3d9_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) mut |chain| {
assert_some_ptr!(mut 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()
};
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
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(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: *mut libra_d3d9_filter_chain_t,
out: *mut MaybeUninit<u32>
) mut |chain| {
assert_some_ptr!(mut chain);
unsafe {
let value = chain.get_enabled_pass_count();
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,7 +3,9 @@ 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 librashader::runtime::gl::{
FilterChain, FilterChainOptions, FrameOptions, GLFramebuffer, GLImage,
};
use std::ffi::CStr;
use std::ffi::{c_char, c_void, CString};
use std::mem::MaybeUninit;
@ -11,8 +13,6 @@ 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::FilterChainParameters;
use librashader::runtime::{Size, Viewport};
@ -64,11 +64,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, 4 = 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,7 +99,7 @@ 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];
}
}
@ -149,7 +156,7 @@ 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 chain = FilterChain::load_from_preset(*preset, options.as_ref())?;
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
chain,
@ -172,7 +179,7 @@ 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,

View file

@ -8,9 +8,33 @@ pub mod gl;
pub mod vk;
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
#[cfg(all(target_os = "windows", feature = "runtime-d3d11"))]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d11")
))]
pub mod d3d11;
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d9")
))]
pub mod d3d9;
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d12")
))]
pub mod d3d12;
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(
target_vendor = "apple",
feature = "runtime-metal",
feature = "__cbindgen_internal_objc"
)
))]
pub mod mtl;

View file

@ -0,0 +1,319 @@
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, 4 = 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.
///
/// ## 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,
viewport: libra_viewport_t,
output: PMTLTexture,
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 = Viewport {
x: viewport.x,
y: viewport.y,
output,
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
) mut |chain| {
assert_some_ptr!(mut chain);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
}
}
}
}
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: *mut libra_mtl_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) mut |chain| {
assert_some_ptr!(mut chain);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
};
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
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(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: *mut libra_mtl_filter_chain_t,
out: *mut MaybeUninit<u32>
) mut |chain| {
assert_some_ptr!(mut chain);
let value = chain.get_enabled_pass_count();
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,20 +3,21 @@ 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::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 ash::vk;
use crate::LIBRASHADER_API_VERSION;
pub use ash::vk::PFN_vkGetInstanceProcAddr;
/// A Vulkan instance function loader that the Vulkan filter chain needs to be initialized with.
@ -82,22 +83,29 @@ impl From<libra_device_vk_t> for VulkanInstance {
}
}
/// 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, 4 = 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 +114,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 +142,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 +170,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 +188,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 +220,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())?;
@ -246,7 +254,7 @@ 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,

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

@ -13,7 +13,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.

View file

@ -0,0 +1,263 @@
//! 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
///
/// 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

@ -3,7 +3,7 @@ name = "librashader-common"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.1.3"
version = "0.3.0"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -14,24 +14,35 @@ description = "RetroArch shaders for all."
[features]
default = []
opengl = ["gl"]
d3d9 = ["windows"]
d3d11 = ["windows", "dxgi"]
d3d12 = ["windows", "dxgi"]
dxgi = ["windows"]
vulkan = ["ash"]
wgpu = ["wgpu-types"]
metal = ["objc2-metal"]
[dependencies]
gl = { version = "0.14.0", optional = true }
ash = { version = "0.37.1+1.3.235", optional = true }
ash = { version = "0.38", optional = true }
wgpu-types = { version = "22", optional = true }
num-traits = "0.2.15"
rustc-hash = "2.0.0"
halfbrown = "0.2.4"
[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-metal]
optional = true
version = "0.2.0"
features = ["MTLPixelFormat", "MTLRenderCommandEncoder", "MTLSampler"]

View file

@ -0,0 +1,77 @@
use crate::{FilterMode, ImageFormat, 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 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

@ -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,13 +28,26 @@ 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 std::convert::Infallible;
use std::str::FromStr;
#[derive(Debug, Clone)]
pub enum ShaderStorage {
Path(std::path::PathBuf),
String(String),
}
#[repr(u32)]
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
/// Supported image formats for textures.

View file

@ -0,0 +1,5 @@
/// Fast optimized hash map type for small sets.
pub type FastHashMap<K, V> =
halfbrown::SizedHashMap<K, V, core::hash::BuildHasherDefault<rustc_hash::FxHasher>, 32>;
pub use halfbrown;

View file

@ -0,0 +1,94 @@
use crate::{FilterMode, ImageFormat, Size, WrapMode};
use objc2_metal::{
MTLPixelFormat, MTLSamplerAddressMode, MTLSamplerMinMagFilter, MTLSamplerMipFilter, 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,
}
}
}

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

@ -3,7 +3,7 @@ name = "librashader-preprocess"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.1.3"
version = "0.3.0"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -14,8 +14,7 @@ description = "RetroArch shaders for all."
[dependencies]
thiserror = "1.0.37"
nom = "7.1.1"
librashader-common = { path = "../librashader-common", version = "0.1.3" }
rustc-hash = "1.1.0"
librashader-common = { path = "../librashader-common", version = "0.3.0" }
encoding_rs = "0.8.31"
[features]
@ -23,6 +22,5 @@ default = [ "line_directives" ]
line_directives = []
[dev-dependencies]
librashader-presets = "0.1.0-rc.3"
glob = "0.3.1"
rayon = "1.6.1"

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

@ -15,9 +15,8 @@ mod stage;
use crate::include::read_source;
pub use error::*;
use librashader_common::map::FastHashMap;
use librashader_common::ImageFormat;
use rustc_hash::FxHashMap;
use std::path::Path;
/// The source file for a single shader pass.
#[derive(Debug, Clone, PartialEq)]
@ -32,7 +31,7 @@ pub struct ShaderSource {
pub name: Option<String>,
/// The list of shader parameters found in the shader source.
pub parameters: FxHashMap<String, ShaderParameter>,
pub parameters: FastHashMap<String, ShaderParameter>,
/// The image format the shader expects.
pub format: ImageFormat,
@ -58,8 +57,8 @@ 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(file: &librashader_common::ShaderStorage) -> Result<ShaderSource, PreprocessError> {
load_shader_source(file)
}
}
@ -78,11 +77,17 @@ impl SourceOutput for String {
}
}
pub(crate) fn load_shader_source(path: impl AsRef<Path>) -> Result<ShaderSource, PreprocessError> {
let source = read_source(path)?;
pub(crate) fn load_shader_source(
file: &librashader_common::ShaderStorage,
) -> Result<ShaderSource, PreprocessError> {
let source = match file {
librashader_common::ShaderStorage::Path(path) => read_source(path)?,
librashader_common::ShaderStorage::String(s) => s.to_string(),
};
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,
@ -100,9 +105,9 @@ mod test {
#[test]
pub fn load_file() {
let result = load_shader_source(
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang",
)
let result = load_shader_source(&librashader_common::ShaderStorage::Path(
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang".into(),
))
.unwrap();
eprintln!("{:#}", result.vertex)
}

View file

@ -132,4 +132,16 @@ 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".to_string(),
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())
}
}

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.3"
version = "0.3.0"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -15,8 +15,14 @@ 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.3" }
librashader-common = { path = "../librashader-common", version = "0.3.0" }
num-traits = "0.2"
once_cell = "1"
# we don't need unicode
regex = { version = "1", default-features = false, features = ["perf"] }
rustversion = "1.0"
os_str_bytes = { version = "6", features = ["conversions"] }
[features]
parse_legacy_glsl = []

View file

@ -0,0 +1,463 @@
// 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"
}
}
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 to_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
}
}
#[rustversion::since(1.74)]
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;
}
}
#[rustversion::before(1.74)]
pub(crate) fn apply_context(path: &mut PathBuf, context: &FastHashMap<String, String>) {
use os_str_bytes::RawOsStr;
static WILDCARD_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\$([A-Z-_]+)\\$").unwrap());
if context.is_empty() {
return;
}
let path_str = RawOsStr::new(path.as_os_str());
let path_bytes = path_str.to_raw_bytes();
// Don't want to do any extra work if there's no match.
if !WILDCARD_REGEX.is_match(&path_bytes) {
return;
}
let mut new_path = PathBuf::with_capacity(path.capacity());
for component in path.components() {
match component {
Component::Normal(path) => {
let haystack = RawOsStr::new(path);
let haystack = haystack.to_raw_bytes();
let replaced =
WILDCARD_REGEX.replace_all(&haystack, |caps: &regex::bytes::Captures| {
let Some(name) = caps.get(1) else {
return caps[0].to_vec();
};
let Ok(key) = std::str::from_utf8(name.as_bytes()) else {
return caps[0].to_vec();
};
if let Some(replacement) = context.get(key) {
return RawOsStr::from_str(replacement).to_raw_bytes().to_vec();
}
return caps[0].to_vec();
});
// SAFETY: The original source is valid encoded bytes, and our replacement is
// valid encoded bytes. This upholds the safety requirements of `from_encoded_bytes_unchecked`.
new_path.push(RawOsStr::assert_cow_from_raw_bytes(&replaced.as_ref()).to_os_str())
}
_ => new_path.push(component),
}
}
// If no wildcards are found within the path, or the path after replacing the wildcards does not exist on disk, the path returned will be unaffected.
if let Ok(true) = new_path.try_exists() {
*path = new_path;
}
}

View file

@ -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

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

View file

@ -7,10 +7,16 @@
//! as input to create a filter chain.
//!
//! Re-exported as [`librashader::presets`](https://docs.rs/librashader/latest/librashader/presets/index.html).
#![feature(drain_filter)]
#![allow(stable_features)]
#![allow(unstable_name_collisions)]
pub mod context;
mod error;
mod extract_if;
mod parse;
mod preset;
pub use context::WildcardContext;
pub use error::*;
pub use preset::*;

View file

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

View file

@ -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,11 @@
use crate::extract_if::MakeExtractIf;
use crate::parse::remove_if;
use crate::parse::value::Value;
use crate::{ParameterConfig, Scale2D, Scaling, ShaderPassConfig, ShaderPreset, TextureConfig};
pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
let textures: Vec<TextureConfig> = values
.drain_filter(|f| matches!(*f, Value::Texture { .. }))
.extract_if(|f| matches!(*f, Value::Texture { .. }))
.map(|value| {
if let Value::Texture {
name,
@ -27,7 +28,7 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
})
.collect();
let parameters: Vec<ParameterConfig> = values
.drain_filter(|f| matches!(*f, Value::Parameter { .. }))
.extract_if(|f| matches!(*f, Value::Parameter { .. }))
.map(|value| {
if let Value::Parameter(name, value) = value {
ParameterConfig { name, value }
@ -64,7 +65,7 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
|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))
.extract_if(|v| v.shader_index() == Some(shader))
.collect();
let scale_type = shader_values.iter().find_map(|f| match f {
Value::ScaleType(_, value) => Some(*value),
@ -114,7 +115,7 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
let shader = ShaderPassConfig {
id,
name,
name: librashader_common::ShaderStorage::Path(name),
alias: shader_values.iter().find_map(|f| match f {
Value::Alias(_, value) => Some(value.to_string()),
_ => None,

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;
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 crate::extract_if::MakeExtractIf;
#[derive(Debug)]
pub enum Value {
ShaderCount(i32),
FeedbackPass(i32),
FeedbackPass(#[allow(unused)] i32),
Shader(i32, PathBuf),
ScaleX(i32, ScaleFactor),
ScaleY(i32, ScaleFactor),
@ -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);
@ -187,7 +196,7 @@ fn load_child_reference_strings(
let mut new_tokens = do_lex(&reference_contents)?;
let new_references: Vec<PathBuf> = new_tokens
.drain_filter(|token| *token.key.fragment() == "#reference")
.extract_if(|token| *token.key.fragment() == "#reference")
.map(|value| PathBuf::from(*value.value.fragment()))
.collect();
@ -202,8 +211,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.to_hashmap();
apply_context(&mut path, &context);
let path = path
.canonicalize()
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
@ -214,12 +231,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() {
@ -232,12 +251,14 @@ pub fn parse_values(
}
let references: Vec<PathBuf> = tokens
.drain_filter(|token| *token.key.fragment() == "#reference")
.extract_if(|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,10 +271,11 @@ 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 tokens.extract_if(|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);
@ -264,7 +286,7 @@ 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 tokens.extract_if(|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);
@ -275,7 +297,7 @@ 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 tokens.extract_if(|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,7 +328,7 @@ 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 tokens.extract_if(|token| texture_names.contains(token.key.fragment())) {
let mut relative_path = path.to_path_buf();
relative_path.push(*token.value.fragment());
relative_path
@ -593,13 +615,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);
let basic = parse_preset(root, WildcardContext::new());
eprintln!("{basic:?}");
assert!(basic.is_ok());
}

View file

@ -10,7 +10,7 @@ pub struct ShaderPassConfig {
/// 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,
pub name: librashader_common::ShaderStorage,
/// The alias of the shader pass if available.
pub alias: Option<String>,
/// The filtering mode that this shader pass should expect.
@ -63,6 +63,8 @@ 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.
@ -119,6 +121,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())),
}
}

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.3"
version = "0.3.0"
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.3"
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.3" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.1.3" }
librashader-presets = { path = "../librashader-presets", version = "0.1.3" }
librashader-common = { path = "../librashader-common", version = "0.3.0" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.3.0" }
librashader-presets = { path = "../librashader-presets", version = "0.3.0" }
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_cross = { package = "librashader-spirv-cross", version = "0.25.1", 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 }
indexmap = { version = "2.1.0", features = [] }
matches = { version = "0.1.10", features = [] }
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" ]
default = ["cross", "naga", "serialize", "wgsl", "msl"]
dxil = ["spirv_cross/hlsl", "spirv-to-dxil"]
wgsl = ["cross", "naga/wgsl-out", "spirv", "rspirv"]
cross = [ "spirv_cross", "spirv_cross/glsl", "spirv_cross/hlsl", "spirv_cross/msl" ]
naga = [ "rspirv", "spirv", "naga/spv-in", "naga/spv-out", "naga/wgsl-out", "naga/msl-out" ]
serialize = [ "serde" ]
msl = [ "spirv_cross/msl", "naga/msl-out" ]
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,22 +1,22 @@
use crate::back::spirv::WriteSpirV;
use crate::back::targets::{OutputTarget, DXIL};
use crate::back::{CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput};
use crate::error::{ShaderCompileError, ShaderReflectError};
use crate::front::SpirvCompilation;
use crate::reflect::cross::glsl::GlslReflect;
use crate::reflect::cross::SpirvCross;
use crate::reflect::ReflectShader;
pub use spirv_to_dxil::DxilObject;
pub use spirv_to_dxil::ShaderModel;
use spirv_to_dxil::{
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 {
impl FromCompilation<SpirvCompilation, SpirvCross> for DXIL {
type Target = DXIL;
type Options = Option<ShaderModel>;
type Context = ();
@ -24,17 +24,15 @@ impl FromCompilation<GlslangCompilation> for DXIL {
+ ReflectShader;
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,
},
})
}
@ -59,6 +57,7 @@ impl CompileShader<DXIL> for WriteSpirV {
register_space: 0,
base_shader_register: 1,
},
shader_model_max: sm,
..RuntimeConfig::default()
};
@ -68,7 +67,6 @@ impl CompileShader<DXIL> for WriteSpirV {
None,
"main",
ShaderStage::Vertex,
sm,
ValidatorVersion::None,
&config,
)
@ -79,7 +77,6 @@ impl CompileShader<DXIL> for WriteSpirV {
None,
"main",
ShaderStage::Fragment,
sm,
ValidatorVersion::None,
&config,
)

View file

@ -0,0 +1,35 @@
use crate::back::targets::GLSL;
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::cross::{CompiledProgram, SpirvCross};
use crate::reflect::ReflectShader;
/// The GLSL version to target.
pub use spirv_cross::glsl::Version as GlslVersion;
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_cross::glsl::Target>,
}
impl FromCompilation<SpirvCompilation, SpirvCross> for GLSL {
type Target = GLSL;
type Options = GlslVersion;
type Context = CrossGlslContext;
type Output = impl CompileShader<Self::Target, Options = GlslVersion, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: GlslReflect::try_from(&compile)?,
})
}
}

View file

@ -0,0 +1,148 @@
use crate::back::targets::HLSL;
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::cross::hlsl::HlslReflect;
use crate::reflect::cross::{CompiledProgram, SpirvCross};
use crate::reflect::ReflectShader;
/// The HLSL shader model version to target.
pub use spirv_cross::hlsl::ShaderModel as HlslShaderModel;
/// 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;
}
return 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;
}
return false;
}
}
/// The context for a HLSL compilation via spirv-cross.
pub struct CrossHlslContext {
/// The compiled HLSL program.
pub artifact: CompiledProgram<spirv_cross::hlsl::Target>,
pub vertex_buffers: HlslBufferAssignments,
pub fragment_buffers: HlslBufferAssignments,
}
impl FromCompilation<SpirvCompilation, SpirvCross> for HLSL {
type Target = HLSL;
type Options = Option<HlslShaderModel>;
type Context = CrossHlslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: 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};
@ -40,26 +43,26 @@ pub trait CompileShader<T: OutputTarget> {
///
/// 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>:
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,
>,
{
}
@ -80,8 +83,14 @@ where
}
}
/// 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.
@ -114,3 +123,14 @@ where
self.backend.reflect(pass_number, semantics)
}
}
#[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,70 @@
use crate::back::targets::MSL;
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::cross::msl::MslReflect;
use crate::reflect::cross::{CompiledProgram, SpirvCross};
use crate::reflect::naga::{Naga, NagaReflect};
use crate::reflect::ReflectShader;
use naga::back::msl::TranslationInfo;
use naga::Module;
/// The HLSL shader model version to target.
pub use spirv_cross::msl::Version as MslVersion;
/// 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_cross::msl::Target>,
}
impl FromCompilation<SpirvCompilation, SpirvCross> for MSL {
type Target = MSL;
type Options = Option<self::MslVersion>;
type Context = CrossMslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: 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,
}
impl FromCompilation<SpirvCompilation, Naga> for MSL {
type Target = MSL;
type Options = Option<self::MslVersion>;
type Context = NagaMslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: NagaReflect::try_from(&compile)?,
})
}
}

View file

@ -1,10 +1,13 @@
use crate::back::targets::SPIRV;
use crate::back::{CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput};
use crate::error::{ShaderCompileError, ShaderReflectError};
use crate::front::GlslangCompilation;
use crate::reflect::cross::GlslReflect;
use crate::front::SpirvCompilation;
use crate::reflect::cross::glsl::GlslReflect;
use crate::reflect::cross::SpirvCross;
use crate::reflect::naga::{Naga, NagaLoweringOptions, NagaReflect};
use crate::reflect::semantics::ShaderSemantics;
use crate::reflect::{ReflectShader, ShaderReflection};
use naga::Module;
pub(crate) struct WriteSpirV {
// rely on GLSL to provide out reflection but we don't actually need the AST.
@ -13,7 +16,7 @@ pub(crate) struct WriteSpirV {
pub(crate) fragment: Vec<u32>,
}
impl FromCompilation<GlslangCompilation> for SPIRV {
impl FromCompilation<SpirvCompilation, SpirvCross> for SPIRV {
type Target = SPIRV;
type Options = Option<()>;
type Context = ();
@ -21,7 +24,7 @@ impl FromCompilation<GlslangCompilation> for SPIRV {
+ ReflectShader;
fn from_compilation(
compile: GlslangCompilation,
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
let reflect = GlslReflect::try_from(&compile)?;
let vertex = compile.vertex;
@ -61,3 +64,30 @@ impl CompileShader<SPIRV> for WriteSpirV {
})
}
}
/// The context for a SPIRV compilation via Naga
pub struct NagaSpirvContext {
pub fragment: Module,
pub vertex: Module,
}
impl FromCompilation<SpirvCompilation, Naga> for SPIRV {
type Target = SPIRV;
type Options = NagaSpirvOptions;
type Context = NagaSpirvContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: NagaReflect::try_from(&compile)?,
})
}
}
pub struct NagaSpirvOptions {
pub lowering: NagaLoweringOptions,
pub version: (u8, u8),
}

View file

@ -5,35 +5,54 @@ pub trait OutputTarget {
}
/// Shader compiler target for GLSL.
#[derive(Debug)]
pub struct GLSL;
/// Shader compiler target for HLSL.
#[derive(Debug)]
pub struct HLSL;
/// Shader compiler target for SPIR-V.
#[derive(Debug)]
pub struct SPIRV;
/// Shader compiler target for MSL.
#[derive(Debug)]
pub struct MSL;
/// Shader compiler target for DXIL.
///
/// The resulting DXIL object is always unvalidated and
/// must be validated using platform APIs before usage.
#[derive(Debug)]
pub struct DXIL;
/// Shader compiler target for WGSL.
///
/// The resulting WGSL will split sampler2Ds into
/// split textures and shaders. Shaders for each texture binding
/// will be in descriptor set 1.
#[derive(Debug)]
pub struct WGSL;
impl OutputTarget for GLSL {
type Output = String;
}
impl OutputTarget for HLSL {
type Output = String;
}
impl OutputTarget for WGSL {
type Output = String;
}
impl OutputTarget for MSL {
type Output = String;
}
impl OutputTarget for SPIRV {
type Output = Vec<u32>;
}
#[cfg(test)]
mod test {
use crate::back::targets::GLSL;
use crate::back::FromCompilation;
use crate::front::GlslangCompilation;
use crate::front::SpirvCompilation;
#[allow(dead_code)]
pub fn test_compile(value: GlslangCompilation) {
pub fn test_compile(value: SpirvCompilation) {
let _x = GLSL::from_compilation(value).unwrap();
}
}

View file

@ -0,0 +1,95 @@
use crate::back::targets::WGSL;
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::naga::{Naga, NagaLoweringOptions, NagaReflect};
use crate::reflect::ReflectShader;
use naga::Module;
/// The context for a WGSL compilation via Naga
pub struct NagaWgslContext {
pub fragment: Module,
pub vertex: Module,
}
impl FromCompilation<SpirvCompilation, Naga> for WGSL {
type Target = WGSL;
type Options = NagaLoweringOptions;
type Context = NagaWgslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: NagaReflect::try_from(&compile)?,
})
}
}
#[cfg(test)]
mod test {
use crate::back::targets::WGSL;
use crate::back::{CompileShader, FromCompilation};
use crate::reflect::naga::NagaLoweringOptions;
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use crate::reflect::ReflectShader;
use bitflags::Flags;
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
#[test]
pub fn test_into() {
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
let result = ShaderSource::load("../test/basic.slang").unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
param.1.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
let compilation = crate::front::SpirvCompilation::try_from(&result).unwrap();
let mut wgsl = WGSL::from_compilation(compilation).unwrap();
wgsl.reflect(
0,
&ShaderSemantics {
uniform_semantics,
texture_semantics: Default::default(),
},
)
.expect("");
let compiled = wgsl
.compile(NagaLoweringOptions {
write_pcb_as_ubo: true,
sampler_bind_group: 1,
})
.unwrap();
println!("{}", compiled.fragment);
// println!("{}", compiled.fragment);
// let mut loader = rspirv::dr::Loader::new();
// rspirv::binary::parse_words(compilation.vertex.as_binary(), &mut loader).unwrap();
// let module = loader.module();
//
// let outputs: Vec<&Instruction> = module
// .types_global_values
// .iter()
// .filter(|i| i.class.opcode == Op::Variable)
// .collect();
//
// println!("{outputs:#?}");
}
}

View file

@ -6,26 +6,46 @@ use thiserror::Error;
#[derive(Error, Debug)]
pub enum ShaderCompileError {
/// Compile error from naga.
#[cfg(feature = "unstable-naga")]
#[cfg(feature = "unstable-naga-in")]
#[error("shader")]
NagaCompileError(Vec<naga::front::glsl::Error>),
/// Compilation error from shaderc (glslang).
#[error("shaderc")]
ShaderCCompileError(#[from] shaderc::Error),
/// Compilation error from glslang.
#[error("error when compiling with glslang: {0}")]
GlslangError(#[from] glslang::error::GlslangError),
/// Error when initializing the shaderc compiler.
#[error("shaderc init")]
ShaderCInitError,
/// Error when initializing the glslang compiler.
#[error("error when initializing glslang")]
CompilerInitError,
/// Error when transpiling from spirv-cross.
#[error("cross")]
#[error("spirv-cross error: {0:?}")]
SpirvCrossCompileError(#[from] spirv_cross::ErrorCode),
/// Error when transpiling from spirv-to-dxil
#[cfg(feature = "dxil")]
#[error("spirv-to-dxil")]
#[cfg(all(target_os = "windows", feature = "dxil"))]
#[error("spirv-to-dxil error: {0:?}")]
SpirvToDxilCompileError(#[from] spirv_to_dxil::SpirvToDxilError),
/// Error when transpiling from naga
#[cfg(all(feature = "wgsl", feature = "naga"))]
#[error("naga error when compiling wgsl: {0:?}")]
NagaWgslError(#[from] naga::back::wgsl::Error),
/// Error when transpiling from naga
#[cfg(feature = "naga")]
#[error("naga error when compiling spirv: {0:?}")]
NagaSpvError(#[from] naga::back::spv::Error),
/// Error when transpiling from naga
#[cfg(all(feature = "naga", feature = "msl"))]
#[error("naga error when compiling msl: {0:?}")]
NagaMslError(#[from] naga::back::msl::Error),
/// Error when transpiling from naga
#[cfg(any(feature = "naga", feature = "wgsl"))]
#[error("naga validation error: {0}")]
NagaValidationError(#[from] naga::WithSpan<naga::valid::ValidationError>),
}
/// The error kind encountered when reflecting shader semantics.
@ -34,6 +54,8 @@ pub enum SemanticsErrorKind {
/// The number of uniform buffers was invalid. Only one UBO is permitted.
InvalidUniformBufferCount(usize),
/// The number of push constant blocks was invalid. Only one push constant block is permitted.
InvalidPushBufferCount(usize),
/// The size of the push constant block was invalid.
InvalidPushBufferSize(u32),
/// The location of a varying was invalid.
InvalidLocation(u32),
@ -43,12 +65,16 @@ pub enum SemanticsErrorKind {
InvalidInputCount(usize),
/// The number of outputs declared was invalid.
InvalidOutputCount(usize),
/// Expected a binding but there was none.
MissingBinding,
/// The declared binding point was invalid.
InvalidBinding(u32),
/// The declared resource type was invalid.
InvalidResourceType,
/// The range of a struct member was invalid.
InvalidRange(u32),
/// The number of entry points in the shader was invalid.
InvalidEntryPointCount(usize),
/// The requested uniform or texture name was not provided semantics.
UnknownSemantics(String),
/// The type of the requested uniform was not compatible with the provided semantics.
@ -59,29 +85,24 @@ pub enum SemanticsErrorKind {
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum ShaderReflectError {
/// Compile error from naga.
#[cfg(feature = "unstable-naga")]
#[error("shader")]
NagaCompileError(#[from] naga::front::spv::Error),
/// Reflection error from spirv-cross.
#[error("spirv")]
#[error("spirv cross error: {0}")]
SpirvCrossError(#[from] spirv_cross::ErrorCode),
/// Error when validating vertex shader semantics.
#[error("error when verifying vertex semantics")]
#[error("error when verifying vertex semantics: {0:?}")]
VertexSemanticError(SemanticsErrorKind),
/// Error when validating fragment shader semantics.
#[error("error when verifying texture semantics")]
#[error("error when verifying texture semantics {0:?}")]
FragmentSemanticError(SemanticsErrorKind),
/// The vertex and fragment shader must have the same UBO binding location.
#[error("vertex and fragment shader must have same binding")]
#[error("vertex and fragment shader must have same UBO binding. declared {vertex} in vertex, got {fragment} in fragment")]
MismatchedUniformBuffer { vertex: u32, fragment: u32 },
/// The filter chain was found to be non causal. A pass tried to access the target output
/// in the future.
#[error("filter chain is non causal")]
#[error("filter chain is non causal: tried to access target {target} in pass {pass}")]
NonCausalFilterChain { pass: usize, target: usize },
/// The offset of the given uniform did not match up in both the vertex and fragment shader.
#[error("mismatched offset")]
#[error("the offset of {semantic} was declared as {expected} but found as {received} in pass {pass}")]
MismatchedOffset {
semantic: String,
expected: usize,
@ -90,7 +111,7 @@ pub enum ShaderReflectError {
pass: usize,
},
/// The size of the given uniform did not match up in both the vertex and fragment shader.
#[error("mismatched component")]
#[error("the size of {semantic} was found declared as {vertex} in vertex shader but found as {fragment} in fragment in pass {pass}")]
MismatchedSize {
semantic: String,
vertex: u32,
@ -98,11 +119,19 @@ pub enum ShaderReflectError {
pass: usize,
},
/// The binding number is already in use.
#[error("the binding is already in use")]
#[error("binding {0} is already in use")]
BindingInUse(u32),
/// Error when transpiling from naga
#[cfg(feature = "naga")]
#[error("naga spirv error: {0}")]
NagaInputError(#[from] naga::front::spv::Error),
/// Error when transpiling from naga
#[cfg(feature = "naga")]
#[error("naga validation error: {0}")]
NagaReflectError(#[from] naga::WithSpan<naga::valid::ValidationError>),
}
#[cfg(feature = "unstable-naga")]
#[cfg(feature = "unstable-naga-in")]
impl From<Vec<naga::front::glsl::Error>> for ShaderCompileError {
fn from(err: Vec<naga::front::glsl::Error>) -> Self {
ShaderCompileError::NagaCompileError(err)

View file

@ -0,0 +1,63 @@
use crate::error::ShaderCompileError;
use glslang::{CompilerOptions, ShaderInput};
use librashader_preprocess::ShaderSource;
use rspirv::binary::Assemble;
use rspirv::dr::Builder;
use crate::front::spirv_passes::{link_input_outputs, load_module};
use crate::front::{ShaderInputCompiler, SpirvCompilation};
/// glslang compiler
pub struct Glslang;
impl ShaderInputCompiler<SpirvCompilation> for Glslang {
fn compile(source: &ShaderSource) -> Result<SpirvCompilation, ShaderCompileError> {
compile_spirv(source)
}
}
pub(crate) fn compile_spirv(source: &ShaderSource) -> Result<SpirvCompilation, ShaderCompileError> {
let compiler = glslang::Compiler::acquire().ok_or(ShaderCompileError::CompilerInitError)?;
let options = CompilerOptions {
source_language: glslang::SourceLanguage::GLSL,
target: glslang::Target::Vulkan {
version: glslang::VulkanVersion::Vulkan1_0,
spirv_version: glslang::SpirvVersion::SPIRV1_0,
},
version_profile: None,
};
let vertex = glslang::ShaderSource::from(source.vertex.as_str());
let vertex = ShaderInput::new(&vertex, glslang::ShaderStage::Vertex, &options, None)?;
let vertex = compiler.create_shader(vertex)?;
let fragment = glslang::ShaderSource::from(source.fragment.as_str());
let fragment = ShaderInput::new(&fragment, glslang::ShaderStage::Fragment, &options, None)?;
let fragment = compiler.create_shader(fragment)?;
let vertex = vertex.compile()?;
let fragment = fragment.compile()?;
let vertex = load_module(&vertex);
let fragment = load_module(&fragment);
let mut fragment = Builder::new_from_module(fragment);
let mut pass = link_input_outputs::LinkInputs::new(&vertex, &mut fragment);
pass.do_pass();
let vertex = vertex.assemble();
let fragment = fragment.module().assemble();
Ok(SpirvCompilation { vertex, fragment })
}
#[cfg(test)]
mod test {
use crate::front::glslang::compile_spirv;
use librashader_preprocess::ShaderSource;
#[test]
pub fn compile_shader() {
let result = ShaderSource::load("../test/basic.slang").unwrap();
let _spirv = compile_spirv(&result).unwrap();
}
}

View file

@ -1,24 +1,49 @@
use crate::error::ShaderCompileError;
use librashader_preprocess::ShaderSource;
use serde::{Deserialize, Serialize};
pub(crate) mod spirv_passes;
#[cfg(feature = "unstable-naga")]
mod naga;
mod glslang;
mod shaderc;
/// The output of a shader compiler that is reflectable.
pub trait ShaderReflectObject: Sized {
/// The compiler that produces this reflect object.
type Compiler;
}
pub use crate::front::shaderc::GlslangCompilation;
#[cfg(feature = "unstable-naga")]
pub use crate::front::naga::NagaCompilation;
pub use crate::front::glslang::Glslang;
/// Trait for types that can compile shader sources into a compilation unit.
pub trait ShaderCompilation: Sized {
pub trait ShaderInputCompiler<O: ShaderReflectObject>: Sized {
/// Compile the input shader source file into a compilation unit.
fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError>;
fn compile(source: &ShaderSource) -> Result<O, ShaderCompileError>;
}
impl<T: for<'a> TryFrom<&'a ShaderSource, Error = ShaderCompileError>> ShaderCompilation for T {
fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError> {
source.try_into()
/// Marker trait for types that are the reflectable outputs of a shader compilation.
impl ShaderReflectObject for SpirvCompilation {
type Compiler = Glslang;
}
/// A reflectable shader compilation via glslang.
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct SpirvCompilation {
pub(crate) vertex: Vec<u32>,
pub(crate) fragment: Vec<u32>,
}
impl SpirvCompilation {
/// Tries to compile SPIR-V from the provided shader source.
pub fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError> {
glslang::compile_spirv(source)
}
}
impl TryFrom<&ShaderSource> for SpirvCompilation {
type Error = ShaderCompileError;
/// Tries to compile SPIR-V from the provided shader source.
fn try_from(source: &ShaderSource) -> Result<Self, Self::Error> {
Glslang::compile(source)
}
}

View file

@ -1,152 +0,0 @@
use crate::error::ShaderCompileError;
use librashader_preprocess::ShaderSource;
use naga::front::glsl::{Options, Parser};
use naga::{Module, ShaderStage};
/// A reflectable shader compilation via naga.
#[derive(Debug)]
pub struct NagaCompilation {
pub(crate) vertex: Module,
pub(crate) fragment: Module,
}
impl TryFrom<&ShaderSource> for NagaCompilation {
type Error = ShaderCompileError;
/// Tries to compile SPIR-V from the provided shader source.
fn try_from(source: &ShaderSource) -> Result<Self, Self::Error> {
compile_spirv(source)
}
}
fn compile_spirv(source: &ShaderSource) -> Result<NagaCompilation, ShaderCompileError> {
let mut parser = Parser::default();
let vertex = parser.parse(&Options::from(ShaderStage::Vertex), &source.vertex)?;
let fragment = parser.parse(&Options::from(ShaderStage::Fragment), &source.fragment)?;
Ok(NagaCompilation { vertex, fragment })
}
#[cfg(test)]
mod test {
use crate::front::naga::compile_spirv;
use crate::front::shaderc::GlslangCompilation;
use librashader_preprocess::ShaderSource;
use naga::back::glsl::{PipelineOptions, Version};
use naga::back::spv::{Capability, WriterFlags};
use naga::front::glsl::{Options, Parser};
use naga::front::spv::Options as SpvOptions;
use naga::valid::{Capabilities, ValidationFlags};
use naga::{FastHashSet, ShaderStage};
use rspirv::binary::Disassemble;
#[test]
pub fn compile_naga_test() {
let result = ShaderSource::load(
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang",
)
.unwrap();
let fragment_source = result.fragment;
let mut parser = Parser::default();
println!("{fragment_source}");
let _fragment = parser
.parse(&Options::from(ShaderStage::Fragment), &fragment_source)
.unwrap();
}
#[test]
pub fn compile_shader() {
let result = ShaderSource::load(
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang",
)
.unwrap();
let spirv = compile_spirv(&result).unwrap();
eprintln!("{spirv:?}")
}
#[test]
pub fn compile_shader_roundtrip() {
let result = ShaderSource::load("../test/basic.slang").unwrap();
let cross = GlslangCompilation::compile(&result).unwrap();
let naga_fragment =
naga::front::spv::parse_u8_slice(cross.fragment.as_binary_u8(), &SpvOptions::default())
.unwrap();
println!("{:#?}", naga_fragment.constants);
println!("{:#?}", naga_fragment.global_variables);
println!("{:#?}", naga_fragment.types);
}
#[test]
pub fn naga_playground() {
let result = ShaderSource::load("../test/basic.slang").unwrap();
let spirv = GlslangCompilation::compile(&result).unwrap();
let module =
naga::front::spv::parse_u8_slice(spirv.fragment.as_binary_u8(), &SpvOptions::default())
.unwrap();
let capability = FastHashSet::from_iter([Capability::Shader]);
let mut writer = naga::back::spv::Writer::new(&naga::back::spv::Options {
lang_version: (1, 0),
flags: WriterFlags::all(),
binding_map: Default::default(),
capabilities: Some(capability),
bounds_check_policies: Default::default(),
})
.unwrap();
let mut validator =
naga::valid::Validator::new(ValidationFlags::empty(), Capabilities::all());
let info = validator.validate(&module).unwrap();
let mut spv_out = Vec::new();
let pipe = naga::back::spv::PipelineOptions {
shader_stage: ShaderStage::Fragment,
entry_point: "main".to_string(),
};
writer
.write(&module, &info, Some(&pipe), &mut spv_out)
.unwrap();
let mut glsl_out = String::new();
let opts = naga::back::glsl::Options {
version: Version::Desktop(330),
writer_flags: naga::back::glsl::WriterFlags::all(),
binding_map: Default::default(),
};
let pipe = PipelineOptions {
shader_stage: ShaderStage::Fragment,
entry_point: "main".to_string(),
multiview: None,
};
let mut glsl_naga = naga::back::glsl::Writer::new(
&mut glsl_out,
&module,
&info,
&opts,
&pipe,
Default::default(),
)
.unwrap();
glsl_naga.write().unwrap();
let wgsl =
naga::back::wgsl::write_string(&module, &info, naga::back::wgsl::WriterFlags::all())
.unwrap();
let mut loader = rspirv::dr::Loader::new();
rspirv::binary::parse_words(&spv_out, &mut loader).unwrap();
let module = loader.module();
println!("--- spirv --");
println!("{:#}", module.disassemble());
println!("--- cross glsl --");
let loaded = spirv_cross::spirv::Module::from_words(&spv_out);
let mut ast = spirv_cross::spirv::Ast::<spirv_cross::glsl::Target>::parse(&loaded).unwrap();
println!("{:#}", ast.compile().unwrap());
println!("--- naga glsl---");
println!("{glsl_out:#}");
println!("--- naga wgsl---");
println!("{wgsl:#}")
}
}

View file

@ -1,162 +0,0 @@
use crate::error::ShaderCompileError;
use librashader_preprocess::ShaderSource;
use shaderc::{CompileOptions, Limit, ShaderKind};
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
/// A reflectable shader compilation via glslang (shaderc).
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct GlslangCompilation {
pub(crate) vertex: Vec<u32>,
pub(crate) fragment: Vec<u32>,
}
impl GlslangCompilation {
/// Tries to compile SPIR-V from the provided shader source.
pub fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError> {
compile_spirv(source)
}
}
impl TryFrom<&ShaderSource> for GlslangCompilation {
type Error = ShaderCompileError;
/// Tries to compile SPIR-V from the provided shader source.
fn try_from(source: &ShaderSource) -> Result<Self, Self::Error> {
GlslangCompilation::compile(source)
}
}
fn get_shaderc_options() -> Result<CompileOptions<'static>, ShaderCompileError> {
let mut options = CompileOptions::new().ok_or(ShaderCompileError::ShaderCInitError)?;
options.set_include_callback(|_, _, _, _| {
Err("RetroArch shaders must already have includes be preprocessed".into())
});
options.set_limit(Limit::MaxLights, 32);
options.set_limit(Limit::MaxClipPlanes, 6);
options.set_limit(Limit::MaxTextureUnits, 32);
options.set_limit(Limit::MaxTextureCoords, 32);
options.set_limit(Limit::MaxVertexAttribs, 64);
options.set_limit(Limit::MaxVertexUniformComponents, 4096);
options.set_limit(Limit::MaxVaryingFloats, 64);
options.set_limit(Limit::MaxVertexTextureImageUnits, 32);
options.set_limit(Limit::MaxCombinedTextureImageUnits, 80);
options.set_limit(Limit::MaxTextureImageUnits, 32);
options.set_limit(Limit::MaxFragmentUniformComponents, 4096);
options.set_limit(Limit::MaxDrawBuffers, 32);
options.set_limit(Limit::MaxVertexUniformVectors, 128);
options.set_limit(Limit::MaxVaryingVectors, 8);
options.set_limit(Limit::MaxFragmentUniformVectors, 16);
options.set_limit(Limit::MaxVertexOutputVectors, 16);
options.set_limit(Limit::MaxFragmentInputVectors, 15);
options.set_limit(Limit::MinProgramTexelOffset, -8);
options.set_limit(Limit::MaxProgramTexelOffset, 7);
options.set_limit(Limit::MaxClipDistances, 8);
options.set_limit(Limit::MaxComputeWorkGroupCountX, 65535);
options.set_limit(Limit::MaxComputeWorkGroupCountY, 65535);
options.set_limit(Limit::MaxComputeWorkGroupCountZ, 65535);
options.set_limit(Limit::MaxComputeWorkGroupSizeX, 1024);
options.set_limit(Limit::MaxComputeWorkGroupSizeY, 1024);
options.set_limit(Limit::MaxComputeWorkGroupSizeZ, 64);
options.set_limit(Limit::MaxComputeUniformComponents, 1024);
options.set_limit(Limit::MaxComputeTextureImageUnits, 16);
options.set_limit(Limit::MaxComputeImageUniforms, 8);
options.set_limit(Limit::MaxComputeAtomicCounters, 8);
options.set_limit(Limit::MaxComputeAtomicCounterBuffers, 1);
options.set_limit(Limit::MaxVaryingComponents, 60);
options.set_limit(Limit::MaxVertexOutputComponents, 64);
options.set_limit(Limit::MaxGeometryInputComponents, 64);
options.set_limit(Limit::MaxGeometryOutputComponents, 128);
options.set_limit(Limit::MaxFragmentInputComponents, 128);
options.set_limit(Limit::MaxImageUnits, 8);
options.set_limit(Limit::MaxCombinedImageUnitsAndFragmentOutputs, 8);
options.set_limit(Limit::MaxCombinedShaderOutputResources, 8);
options.set_limit(Limit::MaxImageSamples, 0);
options.set_limit(Limit::MaxVertexImageUniforms, 0);
options.set_limit(Limit::MaxTessControlImageUniforms, 0);
options.set_limit(Limit::MaxTessEvaluationImageUniforms, 0);
options.set_limit(Limit::MaxGeometryImageUniforms, 0);
options.set_limit(Limit::MaxFragmentImageUniforms, 8);
options.set_limit(Limit::MaxCombinedImageUniforms, 8);
options.set_limit(Limit::MaxGeometryTextureImageUnits, 16);
options.set_limit(Limit::MaxGeometryOutputVertices, 256);
options.set_limit(Limit::MaxGeometryTotalOutputComponents, 1024);
options.set_limit(Limit::MaxGeometryUniformComponents, 1024);
options.set_limit(Limit::MaxGeometryVaryingComponents, 64);
options.set_limit(Limit::MaxTessControlInputComponents, 128);
options.set_limit(Limit::MaxTessControlOutputComponents, 128);
options.set_limit(Limit::MaxTessControlTextureImageUnits, 16);
options.set_limit(Limit::MaxTessControlUniformComponents, 1024);
options.set_limit(Limit::MaxTessControlTotalOutputComponents, 4096);
options.set_limit(Limit::MaxTessEvaluationInputComponents, 128);
options.set_limit(Limit::MaxTessEvaluationOutputComponents, 128);
options.set_limit(Limit::MaxTessEvaluationTextureImageUnits, 16);
options.set_limit(Limit::MaxTessEvaluationUniformComponents, 1024);
options.set_limit(Limit::MaxTessPatchComponents, 120);
options.set_limit(Limit::MaxPatchVertices, 32);
options.set_limit(Limit::MaxTessGenLevel, 64);
options.set_limit(Limit::MaxViewports, 16);
options.set_limit(Limit::MaxVertexAtomicCounters, 0);
options.set_limit(Limit::MaxTessControlAtomicCounters, 0);
options.set_limit(Limit::MaxTessEvaluationAtomicCounters, 0);
options.set_limit(Limit::MaxGeometryAtomicCounters, 0);
options.set_limit(Limit::MaxFragmentAtomicCounters, 8);
options.set_limit(Limit::MaxCombinedAtomicCounters, 8);
options.set_limit(Limit::MaxAtomicCounterBindings, 1);
options.set_limit(Limit::MaxVertexAtomicCounterBuffers, 0);
options.set_limit(Limit::MaxTessControlAtomicCounterBuffers, 0);
options.set_limit(Limit::MaxTessEvaluationAtomicCounterBuffers, 0);
options.set_limit(Limit::MaxGeometryAtomicCounterBuffers, 0);
options.set_limit(Limit::MaxFragmentAtomicCounterBuffers, 1);
options.set_limit(Limit::MaxCombinedAtomicCounterBuffers, 1);
options.set_limit(Limit::MaxAtomicCounterBufferSize, 16384);
options.set_limit(Limit::MaxTransformFeedbackBuffers, 4);
options.set_limit(Limit::MaxTransformFeedbackInterleavedComponents, 64);
options.set_limit(Limit::MaxCullDistances, 8);
options.set_limit(Limit::MaxCombinedClipAndCullDistances, 8);
options.set_limit(Limit::MaxSamples, 4);
Ok(options)
}
pub(crate) fn compile_spirv(
source: &ShaderSource,
) -> Result<GlslangCompilation, ShaderCompileError> {
let compiler = shaderc::Compiler::new().ok_or(ShaderCompileError::ShaderCInitError)?;
let name = source.name.as_deref().unwrap_or("shader.slang");
let options = get_shaderc_options()?;
let vertex = compiler.compile_into_spirv(
&source.vertex,
ShaderKind::Vertex,
name,
"main",
Some(&options),
)?;
let fragment = compiler.compile_into_spirv(
&source.fragment,
ShaderKind::Fragment,
name,
"main",
Some(&options),
)?;
// shaderc has a GIL so Send is unsafe.
let vertex = Vec::from(vertex.as_binary());
let fragment = Vec::from(fragment.as_binary());
Ok(GlslangCompilation { vertex, fragment })
}
#[cfg(test)]
mod test {
use crate::front::shaderc::compile_spirv;
use librashader_preprocess::ShaderSource;
#[test]
pub fn compile_shader() {
let result = ShaderSource::load("../test/basic.slang").unwrap();
let _spirv = compile_spirv(&result).unwrap();
}
}

View file

@ -0,0 +1,151 @@
use rspirv::dr::{Builder, Module, Operand};
use rustc_hash::{FxHashMap, FxHashSet};
use spirv::{Decoration, Op, StorageClass};
pub struct LinkInputs<'a> {
pub frag_builder: &'a mut Builder,
pub inputs: FxHashSet<spirv::Word>,
}
impl<'a> LinkInputs<'a> {
fn find_location(module: &Module, id: spirv::Word) -> Option<u32> {
module.annotations.iter().find_map(|op| {
if op.class.opcode != Op::Decorate {
return None;
}
let Some(Operand::Decoration(Decoration::Location)) = op.operands.get(1) else {
return None;
};
let Some(&Operand::IdRef(target)) = op.operands.get(0) else {
return None;
};
if target != id {
return None;
}
let Some(&Operand::LiteralBit32(binding)) = op.operands.get(2) else {
return None;
};
return Some(binding);
})
}
pub fn new(vert: &'a Module, frag: &'a mut Builder) -> Self {
let mut inputs = FxHashSet::default();
let mut bindings = FxHashMap::default();
for global in frag.module_ref().types_global_values.iter() {
if global.class.opcode == spirv::Op::Variable
&& global.operands[0] == Operand::StorageClass(StorageClass::Input)
{
if let Some(id) = global.result_id {
let Some(location) = Self::find_location(frag.module_ref(), id) else {
continue;
};
inputs.insert(id);
bindings.insert(location, id);
}
}
}
for global in vert.types_global_values.iter() {
if global.class.opcode == spirv::Op::Variable
&& global.operands[0] == Operand::StorageClass(StorageClass::Output)
{
if let Some(id) = global.result_id {
let Some(location) = Self::find_location(vert, id) else {
continue;
};
if let Some(frag_ref) = bindings.get(&location) {
// if something is bound to the same location in the vertex shader,
// we're good.
inputs.remove(&frag_ref);
}
}
}
}
Self {
frag_builder: frag,
inputs,
}
}
pub fn do_pass(&mut self) {
let functions = &self.frag_builder.module_ref().functions;
// literally if it has any reference at all we can keep it
for function in functions {
for param in &function.parameters {
for op in &param.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
self.inputs.remove(&word);
}
}
}
}
for block in &function.blocks {
for inst in &block.instructions {
for op in &inst.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
self.inputs.remove(&word);
}
}
}
}
}
}
// ok well guess we dont
self.frag_builder.module_mut().debug_names.retain(|instr| {
for op in &instr.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
return false;
}
}
}
return true;
});
self.frag_builder.module_mut().annotations.retain(|instr| {
for op in &instr.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
return false;
}
}
}
return true;
});
for entry_point in self.frag_builder.module_mut().entry_points.iter_mut() {
entry_point.operands.retain(|op| {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
return false;
}
}
return true;
})
}
self.frag_builder
.module_mut()
.types_global_values
.retain(|instr| {
let Some(id) = instr.result_id else {
return true;
};
!self.inputs.contains(&id)
});
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
pub mod link_input_outputs;
pub mod lower_samplers;
// Load SPIR-V as an rspirv module
pub(crate) fn load_module(words: &[u32]) -> rspirv::dr::Module {
let mut loader = rspirv::dr::Loader::new();
rspirv::binary::parse_words(words, &mut loader).unwrap();
let module = loader.module();
module
}

View file

@ -13,10 +13,11 @@
//! use librashader_presets::ShaderPreset;
//! use librashader_reflect::back::{CompileReflectShader, FromCompilation};
//! use librashader_reflect::back::targets::SPIRV;
//! use librashader_reflect::front::GlslangCompilation;
//! use librashader_reflect::front::{Glslang, ShaderInputCompiler, SpirvCompilation};
//! use librashader_reflect::reflect::cross::SpirvCross;
//! use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact};
//! use librashader_reflect::reflect::semantics::ShaderSemantics;
//! type Artifact = impl CompileReflectShader<SPIRV, GlslangCompilation>;
//! type Artifact = impl CompileReflectShader<SPIRV, SpirvCompilation, SpirvCross>;
//! type ShaderPassMeta = ShaderPassArtifact<Artifact>;
//!
//! // Compile single shader
@ -24,28 +25,25 @@
//! source: &ShaderSource,
//! ) -> Result<Artifact, Box<dyn Error>>
//! {
//! let compilation = GlslangCompilation::compile(&source)?;
//! let spirv = SPIRV::from_compilation(artifact)?;
//! let compilation = SpirvCompilation::try_from(&source);
//! let spirv = SPIRV::from_compilation(compilation)?;
//! Ok(spirv)
//! }
//!
//! // Compile preset
//! pub fn compile_preset(preset: ShaderPreset) -> Result<(Vec<ShaderPassMeta>, ShaderSemantics), Box<dyn Error>>
//! {
//! let (passes, semantics) = SPIRV::compile_preset_passes::<GlslangCompilation, Box<dyn Error>>(
//! let (passes, semantics) = SPIRV::compile_preset_passes::<SpirvCompilation, SpirvCross, Box<dyn Error>>(
//! preset.shaders, &preset.textures)?;
//! Ok((passes, semantics))
//! }
//! ```
//!
//! ## What's with all the traits?
//! librashader-reflect is designed to be compiler-agnostic. In the future, we will allow usage of
//! [naga](https://docs.rs/naga/latest/naga/index.html), a pure-Rust shader compiler, when it has
//! matured enough to support [the features librashader needs](https://github.com/gfx-rs/naga/issues/1012).
//!
//! In the meanwhile, the only supported compilation type is [GlslangCompilation](crate::front::GlslangCompilation),
//! which does transpilation via [shaderc](https://github.com/google/shaderc) and [SPIRV-Cross](https://github.com/KhronosGroup/SPIRV-Cross).
#![feature(type_alias_impl_trait)]
//! librashader-reflect is designed to be compiler-agnostic. [naga](https://docs.rs/naga/latest/naga/index.html),
//! a pure-Rust shader compiler, as well as SPIRV-Cross via [SpirvCompilation](crate::front::SpirvCompilation)
//! is supported.
#![feature(impl_trait_in_assoc_type)]
#![feature(let_chains)]
/// Shader codegen backends.

View file

@ -0,0 +1,139 @@
use crate::back::glsl::CrossGlslContext;
use crate::back::targets::GLSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
use spirv_cross::spirv::Decoration;
use spirv_cross::ErrorCode;
pub(crate) type GlslReflect = CrossReflect<spirv_cross::glsl::Target>;
impl CompileShader<GLSL> for CrossReflect<spirv_cross::glsl::Target> {
type Options = spirv_cross::glsl::Version;
type Context = CrossGlslContext;
fn compile(
mut self,
version: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
let mut options: spirv_cross::glsl::CompilerOptions = Default::default();
options.version = version;
options.fragment.default_float_precision = spirv_cross::glsl::Precision::High;
options.fragment.default_int_precision = spirv_cross::glsl::Precision::High;
options.enable_420_pack_extension = false;
self.vertex.set_compiler_options(&options)?;
self.fragment.set_compiler_options(&options)?;
let vertex_resources = self.vertex.get_shader_resources()?;
let fragment_resources = self.fragment.get_shader_resources()?;
for res in &vertex_resources.stage_outputs {
// let location = self.vertex.get_decoration(res.id, Decoration::Location)?;
// self.vertex
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
self.vertex.unset_decoration(res.id, Decoration::Location)?;
}
for res in &fragment_resources.stage_inputs {
// let location = self.fragment.get_decoration(res.id, Decoration::Location)?;
// self.fragment
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
self.fragment
.unset_decoration(res.id, Decoration::Location)?;
}
if vertex_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
for res in &vertex_resources.push_constant_buffers {
self.vertex.set_name(res.id, "LIBRA_PUSH_VERTEX_INSTANCE")?;
self.vertex
.set_name(res.base_type_id, "LIBRA_PUSH_VERTEX")?;
}
// todo: options
let _flatten = false;
if vertex_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
for res in &vertex_resources.uniform_buffers {
// if flatten {
// self.vertex.flatten_buffer_block(res.id)?;
// }
self.vertex.set_name(res.id, "LIBRA_UBO_VERTEX_INSTANCE")?;
self.vertex.set_name(res.base_type_id, "LIBRA_UBO_VERTEX")?;
self.vertex
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.vertex.unset_decoration(res.id, Decoration::Binding)?;
}
if fragment_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
for res in &fragment_resources.push_constant_buffers {
self.fragment
.set_name(res.id, "LIBRA_PUSH_FRAGMENT_INSTANCE")?;
self.fragment
.set_name(res.base_type_id, "LIBRA_PUSH_FRAGMENT")?;
}
if fragment_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
for res in &fragment_resources.uniform_buffers {
// if flatten {
// self.fragment.flatten_buffer_block(res.id)?;
// }
self.fragment
.set_name(res.id, "LIBRA_UBO_FRAGMENT_INSTANCE")?;
self.fragment
.set_name(res.base_type_id, "LIBRA_UBO_FRAGMENT")?;
self.fragment
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.fragment
.unset_decoration(res.id, Decoration::Binding)?;
}
let mut texture_fixups = Vec::new();
for res in fragment_resources.sampled_images {
let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?;
self.fragment
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.fragment
.unset_decoration(res.id, Decoration::Binding)?;
let mut name = res.name;
name.push('\0');
texture_fixups.push((name, binding));
}
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossGlslContext {
sampler_bindings: texture_fixups,
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
},
})
}
}

View file

@ -0,0 +1,119 @@
use crate::back::hlsl::{CrossHlslContext, HlslBufferAssignment, HlslBufferAssignments};
use crate::back::targets::HLSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
use spirv_cross::hlsl::ShaderModel as HlslShaderModel;
use spirv_cross::spirv::Decoration;
use spirv_cross::ErrorCode;
pub(crate) type HlslReflect = CrossReflect<spirv_cross::hlsl::Target>;
impl CompileShader<HLSL> for CrossReflect<spirv_cross::hlsl::Target> {
type Options = Option<HlslShaderModel>;
type Context = CrossHlslContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, CrossHlslContext>, ShaderCompileError> {
let sm = options.unwrap_or(HlslShaderModel::V5_0);
let mut options = spirv_cross::hlsl::CompilerOptions::default();
options.shader_model = sm;
self.vertex.set_compiler_options(&options)?;
self.fragment.set_compiler_options(&options)?;
// todo: options
let vertex_resources = self.vertex.get_shader_resources()?;
let fragment_resources = self.fragment.get_shader_resources()?;
let mut vertex_buffer_assignment = HlslBufferAssignments::default();
let mut fragment_buffer_assignment = HlslBufferAssignments::default();
if vertex_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
if let Some(buf) = vertex_resources.uniform_buffers.first() {
vertex_buffer_assignment.ubo = Some(HlslBufferAssignment {
name: buf.name.clone(),
id: buf.id,
})
}
if vertex_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
if let Some(buf) = vertex_resources.push_constant_buffers.first() {
vertex_buffer_assignment.push = Some(HlslBufferAssignment {
name: buf.name.clone(),
id: buf.id,
})
}
if fragment_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
if let Some(buf) = fragment_resources.uniform_buffers.first() {
fragment_buffer_assignment.ubo = Some(HlslBufferAssignment {
name: buf.name.clone(),
id: buf.id,
})
}
if fragment_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
if let Some(buf) = fragment_resources.push_constant_buffers.first() {
fragment_buffer_assignment.push = Some(HlslBufferAssignment {
name: buf.name.clone(),
id: buf.id,
})
}
if sm == HlslShaderModel::V3_0 {
for res in &fragment_resources.sampled_images {
let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?;
self.fragment
.set_name(res.id, &format!("LIBRA_SAMPLER2D_{binding}"))?;
// self.fragment
// .unset_decoration(res.id, Decoration::Binding)?;
}
}
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossHlslContext {
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
vertex_buffers: vertex_buffer_assignment,
fragment_buffers: fragment_buffer_assignment,
},
})
}
}

View file

@ -1,28 +1,48 @@
use crate::error::{SemanticsErrorKind, ShaderCompileError, ShaderReflectError};
use crate::front::GlslangCompilation;
#[doc(hidden)]
pub mod glsl;
#[doc(hidden)]
pub mod hlsl;
#[doc(hidden)]
pub mod msl;
use crate::error::{SemanticsErrorKind, ShaderReflectError};
use crate::front::SpirvCompilation;
use crate::reflect::semantics::{
BindingMeta, BindingStage, MemberOffset, PushReflection, ShaderReflection, ShaderSemantics,
TextureBinding, TextureSemanticMap, TextureSemantics, TextureSizeMeta, TypeInfo, UboReflection,
BindingMeta, BindingStage, BufferReflection, MemberOffset, ShaderReflection, ShaderSemantics,
TextureBinding, TextureSemanticMap, TextureSemantics, TextureSizeMeta, TypeInfo,
UniformMemberBlock, UniqueSemanticMap, UniqueSemantics, ValidateTypeSemantics, VariableMeta,
MAX_BINDINGS_COUNT, MAX_PUSH_BUFFER_SIZE,
};
use crate::reflect::{align_uniform_size, ReflectShader};
use std::fmt::Debug;
use std::ops::Deref;
use spirv_cross::spirv::{Ast, Decoration, Module, Resource, ShaderResources, Type};
use spirv_cross::{glsl, hlsl, ErrorCode};
use spirv_cross::ErrorCode;
use crate::back::cross::{CrossGlslContext, CrossHlslContext, HlslShaderModel};
use crate::back::targets::{GLSL, HLSL};
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::reflect::helper::{SemanticErrorBlame, TextureData, UboData};
/// Reflect shaders under SPIRV-Cross semantics.
///
/// SPIRV-Cross supports GLSL, HLSL, SPIR-V, and MSL targets.
#[derive(Debug)]
pub struct SpirvCross;
// This is "probably" OK.
unsafe impl<T: Send + spirv_cross::spirv::Target> Send for CrossReflect<T> {}
unsafe impl<T: Send + spirv_cross::spirv::Target> Send for CrossReflect<T>
where
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
{
}
pub(crate) struct CrossReflect<T>
where
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
{
vertex: Ast<T>,
fragment: Ast<T>,
@ -49,15 +69,28 @@ where
pub fragment: CompiledAst<T>,
}
pub(crate) type HlslReflect = CrossReflect<hlsl::Target>;
pub(crate) type GlslReflect = CrossReflect<glsl::Target>;
impl ValidateTypeSemantics<Type> for UniqueSemantics {
fn validate_type(&self, ty: &Type) -> Option<TypeInfo> {
let (Type::Float { ref array, vecsize, columns, .. }
| Type::Int { ref array, vecsize, columns, .. }
| Type::UInt { ref array, vecsize, columns, .. }) = *ty else {
return None
let (Type::Float {
ref array,
vecsize,
columns,
..
}
| Type::Int {
ref array,
vecsize,
columns,
..
}
| Type::UInt {
ref array,
vecsize,
columns,
..
}) = *ty
else {
return None;
};
if !array.is_empty() {
@ -68,7 +101,10 @@ impl ValidateTypeSemantics<Type> for UniqueSemantics {
UniqueSemantics::MVP => {
matches!(ty, Type::Float { .. }) && vecsize == 4 && columns == 4
}
UniqueSemantics::FrameCount => {
UniqueSemantics::FrameCount
| UniqueSemantics::Rotation
| UniqueSemantics::TotalSubFrames
| UniqueSemantics::CurrentSubFrame => {
matches!(ty, Type::UInt { .. }) && vecsize == 1 && columns == 1
}
UniqueSemantics::FrameDirection => {
@ -93,8 +129,14 @@ impl ValidateTypeSemantics<Type> for UniqueSemantics {
impl ValidateTypeSemantics<Type> for TextureSemantics {
fn validate_type(&self, ty: &Type) -> Option<TypeInfo> {
let Type::Float { ref array, vecsize, columns, .. } = *ty else {
return None
let Type::Float {
ref array,
vecsize,
columns,
..
} = *ty
else {
return None;
};
if !array.is_empty() {
@ -112,7 +154,7 @@ impl ValidateTypeSemantics<Type> for TextureSemantics {
}
}
impl<T> TryFrom<&GlslangCompilation> for CrossReflect<T>
impl<T> TryFrom<&SpirvCompilation> for CrossReflect<T>
where
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
@ -120,7 +162,7 @@ where
{
type Error = ShaderReflectError;
fn try_from(value: &GlslangCompilation) -> Result<Self, Self::Error> {
fn try_from(value: &SpirvCompilation) -> Result<Self, Self::Error> {
let vertex_module = Module::from_words(&value.vertex);
let fragment_module = Module::from_words(&value.fragment);
@ -186,6 +228,7 @@ where
));
}
// Ensure that vertex attributes use location 0 and 1
let vert_mask = vertex_res.stage_inputs.iter().try_fold(0, |mask, input| {
Ok::<u32, ErrorCode>(
mask | 1 << self.vertex.get_decoration(input.id, Decoration::Location)?,
@ -205,9 +248,7 @@ where
if vertex_res.push_constant_buffers.len() > 1 {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidUniformBufferCount(
vertex_res.push_constant_buffers.len(),
),
SemanticsErrorKind::InvalidPushBufferCount(vertex_res.push_constant_buffers.len()),
));
}
@ -219,7 +260,7 @@ where
if fragment_res.push_constant_buffers.len() > 1 {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidUniformBufferCount(
SemanticsErrorKind::InvalidPushBufferCount(
fragment_res.push_constant_buffers.len(),
),
));
@ -295,7 +336,7 @@ where
if let Some(parameter) = semantics.uniform_semantics.get_unique_semantic(&name) {
let Some(typeinfo) = parameter.semantics.validate_type(&range_type) else {
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)))
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)));
};
match &parameter.semantics {
@ -310,7 +351,7 @@ where
expected,
received: offset,
ty: offset_type,
pass: pass_number
pass: pass_number,
});
}
if meta.size != typeinfo.size {
@ -345,7 +386,7 @@ where
expected,
received: offset,
ty: offset_type,
pass: pass_number
pass: pass_number,
});
}
if meta.size != typeinfo.size * typeinfo.columns {
@ -372,7 +413,7 @@ where
}
} else if let Some(texture) = semantics.uniform_semantics.get_texture_semantic(&name) {
let Some(_typeinfo) = texture.semantics.validate_type(&range_type) else {
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)))
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)));
};
if let TextureSemantics::PassOutput = texture.semantics {
@ -394,7 +435,7 @@ where
expected,
received: offset,
ty: offset_type,
pass: pass_number
pass: pass_number,
});
}
@ -428,7 +469,7 @@ where
&mut self,
vertex_ubo: Option<&Resource>,
fragment_ubo: Option<&Resource>,
) -> Result<Option<UboReflection>, ShaderReflectError> {
) -> Result<Option<BufferReflection<u32>>, ShaderReflectError> {
if let Some(vertex_ubo) = vertex_ubo {
self.vertex
.set_decoration(vertex_ubo.id, Decoration::Binding, 0)?;
@ -454,7 +495,7 @@ where
}
let size = std::cmp::max(vertex_ubo.size, fragment_ubo.size);
Ok(Some(UboReflection {
Ok(Some(BufferReflection {
binding: vertex_ubo.binding,
size: align_uniform_size(size),
stage_mask: BindingStage::VERTEX | BindingStage::FRAGMENT,
@ -463,7 +504,7 @@ where
(Some(vertex_ubo), None) => {
let vertex_ubo =
Self::get_ubo_data(&self.vertex, vertex_ubo, SemanticErrorBlame::Vertex)?;
Ok(Some(UboReflection {
Ok(Some(BufferReflection {
binding: vertex_ubo.binding,
size: align_uniform_size(vertex_ubo.size),
stage_mask: BindingStage::VERTEX,
@ -472,7 +513,7 @@ where
(None, Some(fragment_ubo)) => {
let fragment_ubo =
Self::get_ubo_data(&self.fragment, fragment_ubo, SemanticErrorBlame::Fragment)?;
Ok(Some(UboReflection {
Ok(Some(BufferReflection {
binding: fragment_ubo.binding,
size: align_uniform_size(fragment_ubo.size),
stage_mask: BindingStage::FRAGMENT,
@ -488,8 +529,15 @@ where
semantics: &ShaderSemantics,
meta: &mut BindingMeta,
) -> Result<(), ShaderReflectError> {
let Some(semantic) = semantics.texture_semantics.get_texture_semantic(texture.name) else {
return Err(SemanticErrorBlame::Fragment.error(SemanticsErrorKind::UnknownSemantics(texture.name.to_string())))
let Some(semantic) = semantics
.texture_semantics
.get_texture_semantic(texture.name)
else {
return Err(
SemanticErrorBlame::Fragment.error(SemanticsErrorKind::UnknownSemantics(
texture.name.to_string(),
)),
);
};
if semantic.semantics == TextureSemantics::PassOutput && semantic.index >= pass_number {
@ -541,7 +589,7 @@ where
&mut self,
vertex_pcb: Option<&Resource>,
fragment_pcb: Option<&Resource>,
) -> Result<Option<PushReflection>, ShaderReflectError> {
) -> Result<Option<BufferReflection<Option<u32>>>, ShaderReflectError> {
if let Some(vertex_pcb) = vertex_pcb {
self.vertex
.set_decoration(vertex_pcb.id, Decoration::Binding, 1)?;
@ -565,7 +613,8 @@ where
let size = std::cmp::max(vertex_size, fragment_size);
Ok(Some(PushReflection {
Ok(Some(BufferReflection {
binding: None,
size: align_uniform_size(size),
stage_mask: BindingStage::VERTEX | BindingStage::FRAGMENT,
}))
@ -573,7 +622,8 @@ where
(Some(vertex_push), None) => {
let vertex_size =
Self::get_push_size(&self.vertex, vertex_push, SemanticErrorBlame::Vertex)?;
Ok(Some(PushReflection {
Ok(Some(BufferReflection {
binding: None,
size: align_uniform_size(vertex_size),
stage_mask: BindingStage::VERTEX,
}))
@ -584,7 +634,8 @@ where
fragment_push,
SemanticErrorBlame::Fragment,
)?;
Ok(Some(PushReflection {
Ok(Some(BufferReflection {
binding: None,
size: align_uniform_size(fragment_size),
stage_mask: BindingStage::FRAGMENT,
}))
@ -691,184 +742,30 @@ where
}
}
impl CompileShader<GLSL> for CrossReflect<glsl::Target> {
type Options = glsl::Version;
type Context = CrossGlslContext;
fn compile(
mut self,
version: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
let mut options: glsl::CompilerOptions = Default::default();
options.version = version;
options.fragment.default_float_precision = glsl::Precision::High;
options.fragment.default_int_precision = glsl::Precision::High;
options.enable_420_pack_extension = false;
self.vertex.set_compiler_options(&options)?;
self.fragment.set_compiler_options(&options)?;
let vertex_resources = self.vertex.get_shader_resources()?;
let fragment_resources = self.fragment.get_shader_resources()?;
for res in &vertex_resources.stage_inputs {
self.vertex.unset_decoration(res.id, Decoration::Location)?;
}
for res in &vertex_resources.stage_outputs {
// let location = self.vertex.get_decoration(res.id, Decoration::Location)?;
// self.vertex
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
self.vertex.unset_decoration(res.id, Decoration::Location)?;
}
for res in &fragment_resources.stage_inputs {
// let location = self.fragment.get_decoration(res.id, Decoration::Location)?;
// self.fragment
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
self.fragment
.unset_decoration(res.id, Decoration::Location)?;
}
if vertex_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
for res in &vertex_resources.push_constant_buffers {
self.vertex.set_name(res.id, "LIBRA_PUSH_VERTEX_INSTANCE")?;
self.vertex
.set_name(res.base_type_id, "LIBRA_PUSH_VERTEX")?;
}
// todo: options
let _flatten = false;
if vertex_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
for res in &vertex_resources.uniform_buffers {
// if flatten {
// self.vertex.flatten_buffer_block(res.id)?;
// }
self.vertex.set_name(res.id, "LIBRA_UBO_VERTEX_INSTANCE")?;
self.vertex.set_name(res.base_type_id, "LIBRA_UBO_VERTEX")?;
self.vertex
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.vertex.unset_decoration(res.id, Decoration::Binding)?;
}
if fragment_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
for res in &fragment_resources.push_constant_buffers {
self.fragment
.set_name(res.id, "LIBRA_PUSH_FRAGMENT_INSTANCE")?;
self.fragment
.set_name(res.base_type_id, "LIBRA_PUSH_FRAGMENT")?;
}
if fragment_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
for res in &fragment_resources.uniform_buffers {
// if flatten {
// self.fragment.flatten_buffer_block(res.id)?;
// }
self.fragment
.set_name(res.id, "LIBRA_UBO_FRAGMENT_INSTANCE")?;
self.fragment
.set_name(res.base_type_id, "LIBRA_UBO_FRAGMENT")?;
self.fragment
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.fragment
.unset_decoration(res.id, Decoration::Binding)?;
}
let mut texture_fixups = Vec::new();
for res in fragment_resources.sampled_images {
let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?;
self.fragment
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.fragment
.unset_decoration(res.id, Decoration::Binding)?;
let mut name = res.name;
name.push('\0');
texture_fixups.push((name, binding));
}
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossGlslContext {
sampler_bindings: texture_fixups,
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
},
})
}
}
impl CompileShader<HLSL> for CrossReflect<hlsl::Target> {
type Options = Option<HlslShaderModel>;
type Context = CrossHlslContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, CrossHlslContext>, ShaderCompileError> {
let sm = options.unwrap_or(HlslShaderModel::V5_0);
let mut options = hlsl::CompilerOptions::default();
options.shader_model = sm;
self.vertex.set_compiler_options(&options)?;
self.fragment.set_compiler_options(&options)?;
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossHlslContext {
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
},
})
}
}
#[cfg(test)]
mod test {
use crate::reflect::cross::CrossReflect;
use crate::reflect::ReflectShader;
use rustc_hash::FxHashMap;
use crate::back::CompileShader;
use crate::front::GlslangCompilation;
use crate::back::hlsl::CrossHlslContext;
use crate::back::targets::HLSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::front::{Glslang, ShaderInputCompiler};
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
use spirv_cross::glsl;
use spirv_cross::glsl::{CompilerOptions, Version};
use spirv_cross::hlsl::ShaderModel;
use spirv_cross::{glsl, hlsl};
#[test]
pub fn test_into() {
let result = ShaderSource::load("../test/basic.slang").unwrap();
let mut uniform_semantics: FxHashMap<String, UniformSemantic> = Default::default();
let result = ShaderSource::load(&librashader_common::ShaderStorage::Path(
"../test/basic.slang".into(),
))
.unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
@ -879,9 +776,9 @@ mod test {
}),
);
}
let spirv = GlslangCompilation::compile(&result).unwrap();
let mut reflect = CrossReflect::<glsl::Target>::try_from(&spirv).unwrap();
let _shader_reflection = reflect
let spirv = Glslang::compile(&result).unwrap();
let mut reflect = CrossReflect::<hlsl::Target>::try_from(&spirv).unwrap();
let shader_reflection = reflect
.reflect(
0,
&ShaderSemantics {
@ -890,12 +787,22 @@ mod test {
},
)
.unwrap();
let mut opts = CompilerOptions::default();
opts.version = Version::V4_60;
opts.enable_420_pack_extension = false;
let compiled = reflect.compile(Version::V3_30).unwrap();
// eprintln!("{shader_reflection:#?}");
eprintln!("{:#}", compiled.fragment)
let mut opts = hlsl::CompilerOptions::default();
opts.shader_model = ShaderModel::V3_0;
let compiled: ShaderCompilerOutput<String, CrossHlslContext> =
<CrossReflect<hlsl::Target> as CompileShader<HLSL>>::compile(
reflect,
Some(ShaderModel::V3_0),
)
.unwrap();
println!("{:?}", shader_reflection.meta);
println!("{}", compiled.fragment);
println!("{}", compiled.vertex);
// // eprintln!("{shader_reflection:#?}");
// eprintln!("{}", compiled.fragment)
// let mut loader = rspirv::dr::Loader::new();
// rspirv::binary::parse_words(spirv.fragment.as_binary(), &mut loader).unwrap();
// let module = loader.module();

View file

@ -0,0 +1,158 @@
use crate::back::msl::CrossMslContext;
use crate::back::targets::MSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
use spirv_cross::msl;
use spirv_cross::msl::{ResourceBinding, ResourceBindingLocation};
use spirv_cross::spirv::{Ast, Decoration, ExecutionModel};
use std::collections::BTreeMap;
pub(crate) type MslReflect = CrossReflect<spirv_cross::msl::Target>;
impl CompileShader<MSL> for CrossReflect<spirv_cross::msl::Target> {
type Options = Option<spirv_cross::msl::Version>;
type Context = CrossMslContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, CrossMslContext>, ShaderCompileError> {
let version = options.unwrap_or(msl::Version::V2_0);
let mut vert_options = spirv_cross::msl::CompilerOptions::default();
let mut frag_options = spirv_cross::msl::CompilerOptions::default();
vert_options.version = version;
frag_options.version = version;
fn set_bindings(
ast: &Ast<msl::Target>,
stage: ExecutionModel,
binding_map: &mut BTreeMap<ResourceBindingLocation, ResourceBinding>,
) -> Result<(), ShaderCompileError> {
let resources = ast.get_shader_resources()?;
for resource in &resources.push_constant_buffers {
let location = ResourceBindingLocation {
stage,
desc_set: msl::PUSH_CONSTANT_DESCRIPTOR_SET,
binding: msl::PUSH_CONSTANT_BINDING,
};
let overridden = ResourceBinding {
buffer_id: ast.get_decoration(resource.id, Decoration::Binding)?,
texture_id: 0,
sampler_id: 0,
base_type: None,
count: 0, // no arrays allowed in slang shaders, otherwise we'd have to get the type and get the array length
};
binding_map.insert(location, overridden);
}
for resource in resources
.uniform_buffers
.iter()
.chain(resources.sampled_images.iter())
{
let binding = ast.get_decoration(resource.id, Decoration::Binding)?;
let location = ResourceBindingLocation {
stage,
desc_set: ast.get_decoration(resource.id, Decoration::DescriptorSet)?,
binding,
};
let overridden = ResourceBinding {
buffer_id: binding,
texture_id: binding,
sampler_id: binding,
base_type: None,
count: 0, // no arrays allowed in slang shaders, otherwise we'd have to get the type and get the array length
};
binding_map.insert(location, overridden);
}
Ok(())
}
set_bindings(
&self.vertex,
ExecutionModel::Vertex,
&mut vert_options.resource_binding_overrides,
)?;
set_bindings(
&self.fragment,
ExecutionModel::Fragment,
&mut frag_options.resource_binding_overrides,
)?;
self.vertex.set_compiler_options(&vert_options)?;
self.fragment.set_compiler_options(&frag_options)?;
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossMslContext {
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
},
})
}
}
#[cfg(test)]
mod test {
use crate::back::targets::{MSL, WGSL};
use crate::back::{CompileShader, FromCompilation};
use crate::reflect::cross::SpirvCross;
use crate::reflect::naga::{Naga, NagaLoweringOptions};
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use crate::reflect::ReflectShader;
use bitflags::Flags;
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
use rustc_hash::FxHashMap;
use spirv_cross::msl;
use std::io::Write;
#[test]
pub fn test_into() {
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
let result = ShaderSource::load(&librashader_common::ShaderStorage::Path(
"../test/basic.slang".into(),
))
.unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
param.1.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
let compilation = crate::front::SpirvCompilation::try_from(&result).unwrap();
let mut msl =
<MSL as FromCompilation<_, SpirvCross>>::from_compilation(compilation).unwrap();
msl.reflect(
0,
&ShaderSemantics {
uniform_semantics,
texture_semantics: Default::default(),
},
)
.expect("");
let compiled = msl.compile(Some(msl::Version::V2_0)).unwrap();
println!("{}", compiled.vertex);
}
}

View file

@ -12,8 +12,9 @@ pub mod presets;
mod helper;
#[cfg(feature = "unstable-naga")]
mod naga;
/// Reflection via naga.
#[cfg(feature = "naga")]
pub mod naga;
/// A trait for compilation outputs that can provide reflection information.
pub trait ReflectShader {

View file

@ -1,126 +0,0 @@
use crate::error::{SemanticsErrorKind, ShaderReflectError};
use crate::front::GlslangCompilation;
use crate::front::NagaCompilation;
use naga::front::spv::Options;
use naga::{GlobalVariable, Module, StructMember, Type, TypeInner};
use std::convert::Infallible;
#[derive(Debug)]
pub struct NagaReflect {
vertex: Module,
fragment: Module,
}
impl TryFrom<NagaCompilation> for NagaReflect {
type Error = ShaderReflectError;
fn try_from(value: NagaCompilation) -> Result<Self, Self::Error> {
Ok(NagaReflect {
vertex: value.vertex,
fragment: value.fragment,
})
}
}
impl TryFrom<&GlslangCompilation> for NagaReflect {
type Error = ShaderReflectError;
fn try_from(value: &GlslangCompilation) -> Result<Self, Self::Error> {
let ops = Options::default();
let vertex =
naga::front::spv::Parser::new(value.vertex.clone().into_iter(), &ops).parse()?;
let fragment =
naga::front::spv::Parser::new(value.fragment.clone().into_iter(), &ops).parse()?;
Ok(NagaReflect { vertex, fragment })
}
}
struct UboData {
// id: u32,
// descriptor_set: u32,
binding: u32,
size: u32,
}
struct Ubo {
members: Vec<StructMember>,
span: u32,
}
impl TryFrom<naga::Type> for Ubo {
type Error = Infallible;
fn try_from(value: Type) -> Result<Self, Infallible> {
match value.inner {
TypeInner::Struct { members, span } => Ok(Ubo { members, span }),
// todo: make this programmer error
_ => panic!(),
}
}
}
impl NagaReflect {
// pub fn get_ubo_data(arena: Arena, variable: GlobalVariable, blame: SemanticErrorBlame) -> Result<UboData, ShaderReflectError> {
// let binding = match variable.binding {
// Some(ResourceBinding { group: 0, binding }) => binding,
// Some(ResourceBinding { group, .. }) => return Err(blame.error(SemanticsErrorKind::InvalidDescriptorSet(group))),
// None => return Err(blame.error(SemanticsErrorKind::InvalidDescriptorSet(u32::MAX))),
// };
//
// if binding >= MAX_BINDINGS_COUNT {
// return Err(blame.error(SemanticsErrorKind::InvalidBinding(binding)));
// }
//
// match variable.ty.as {
// Handle { .. } => {}
// }
// Ok(UboData {
// binding,
//
// })
// }
pub fn reflect_ubos(
vertex: GlobalVariable,
fragment: GlobalVariable,
) -> Result<(), ShaderReflectError> {
match (vertex.binding, fragment.binding) {
// todo: should emit for both but whatever
(None, None) | (Some(_), None) | (None, Some(_)) => {
ShaderReflectError::VertexSemanticError(SemanticsErrorKind::InvalidDescriptorSet(
u32::MAX,
))
}
(Some(_vert), Some(_frag)) => {
todo!();
}
};
todo!();
Ok(())
}
}
#[cfg(test)]
mod test {
use librashader_preprocess::ShaderSource;
use rspirv::dr::Instruction;
use rspirv::spirv::Op;
#[test]
pub fn test_into() {
let result = ShaderSource::load("../test/slang-shaders/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
let compilation = crate::front::GlslangCompilation::try_from(&result).unwrap();
let mut loader = rspirv::dr::Loader::new();
rspirv::binary::parse_words(compilation.vertex.as_binary(), &mut loader).unwrap();
let module = loader.module();
let outputs: Vec<&Instruction> = module
.types_global_values
.iter()
.filter(|i| i.class.opcode == Op::Variable)
.collect();
println!("{outputs:#?}");
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,212 @@
use crate::back::msl::{MslVersion, NagaMslContext, NagaMslModule};
use crate::back::targets::MSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::naga::{NagaLoweringOptions, NagaReflect};
use naga::back::msl::{
BindSamplerTarget, BindTarget, EntryPointResources, Options, PipelineOptions, TranslationInfo,
};
use naga::valid::{Capabilities, ValidationFlags};
use naga::{Module, TypeInner};
use spirv_cross::msl::Version;
fn msl_version_to_naga_msl(version: MslVersion) -> (u8, u8) {
match version {
Version::V1_0 => (1, 0),
Version::V1_1 => (1, 1),
Version::V1_2 => (1, 2),
Version::V2_0 => (2, 0),
Version::V2_1 => (2, 1),
Version::V2_2 => (2, 2),
Version::V2_3 => (2, 3),
_ => (0, 0),
}
}
impl CompileShader<MSL> for NagaReflect {
type Options = Option<crate::back::msl::MslVersion>;
type Context = NagaMslContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
// https://github.com/libretro/RetroArch/blob/434e94c782af2e4d4277a24b7ed8e5fc54870088/gfx/drivers_shader/slang_process.cpp#L524
let lang_version = msl_version_to_naga_msl(options.unwrap_or(MslVersion::V2_0));
let mut vert_options = Options {
lang_version,
per_entry_point_map: Default::default(),
inline_samplers: vec![],
spirv_cross_compatibility: true,
fake_missing_bindings: false,
bounds_check_policies: Default::default(),
zero_initialize_workgroup_memory: false,
};
let mut frag_options = vert_options.clone();
fn write_msl(
module: &Module,
options: Options,
) -> Result<(String, TranslationInfo), ShaderCompileError> {
let mut valid =
naga::valid::Validator::new(ValidationFlags::all(), Capabilities::empty());
let info = valid.validate(&module)?;
let pipeline_options = PipelineOptions {
allow_and_force_point_size: false,
vertex_pulling_transform: false,
vertex_buffer_mappings: vec![],
};
let msl = naga::back::msl::write_string(&module, &info, &options, &pipeline_options)?;
Ok(msl)
}
fn generate_bindings(module: &Module) -> EntryPointResources {
let mut resources = EntryPointResources::default();
let binding_map = &mut resources.resources;
// Don't set PCB because they'll be gone after lowering..
// resources.push_constant_buffer = Some(1u8);
for (_, variable) in module.global_variables.iter() {
let Some(binding) = &variable.binding else {
continue;
};
let Ok(ty) = module.types.get_handle(variable.ty) else {
continue;
};
match ty.inner {
TypeInner::Sampler { .. } => {
binding_map.insert(
binding.clone(),
BindTarget {
buffer: None,
texture: None,
sampler: Some(BindSamplerTarget::Resource(binding.binding as u8)),
binding_array_size: None,
mutable: false,
},
);
}
TypeInner::Struct { .. } => {
binding_map.insert(
binding.clone(),
BindTarget {
buffer: Some(binding.binding as u8),
texture: None,
sampler: None,
binding_array_size: None,
mutable: false,
},
);
}
TypeInner::Image { .. } => {
binding_map.insert(
binding.clone(),
BindTarget {
buffer: None,
texture: Some(binding.binding as u8),
sampler: None,
binding_array_size: None,
mutable: false,
},
);
}
_ => continue,
}
}
resources
}
self.do_lowering(&NagaLoweringOptions {
write_pcb_as_ubo: true,
sampler_bind_group: 1,
});
frag_options
.per_entry_point_map
.insert(String::from("main"), generate_bindings(&self.fragment));
vert_options
.per_entry_point_map
.insert(String::from("main"), generate_bindings(&self.vertex));
let fragment = write_msl(&self.fragment, frag_options)?;
let vertex = write_msl(&self.vertex, vert_options)?;
let vertex_binding = self.get_next_binding(0);
Ok(ShaderCompilerOutput {
vertex: vertex.0,
fragment: fragment.0,
context: NagaMslContext {
fragment: NagaMslModule {
translation_info: fragment.1,
module: self.fragment,
},
vertex: NagaMslModule {
translation_info: vertex.1,
module: self.vertex,
},
next_free_binding: vertex_binding,
},
})
}
}
#[cfg(test)]
mod test {
use crate::back::targets::MSL;
use crate::back::{CompileShader, FromCompilation};
use crate::reflect::naga::{Naga, NagaLoweringOptions};
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use crate::reflect::ReflectShader;
use bitflags::Flags;
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
use spirv_cross::msl;
#[test]
pub fn test_into() {
let result = ShaderSource::load(&librashader_common::ShaderStorage::Path(
"../test/basic.slang".into(),
))
.unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
param.1.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
let compilation = crate::front::SpirvCompilation::try_from(&result).unwrap();
let mut msl = <MSL as FromCompilation<_, Naga>>::from_compilation(compilation).unwrap();
msl.reflect(
0,
&ShaderSemantics {
uniform_semantics,
texture_semantics: Default::default(),
},
)
.expect("");
let compiled = msl.compile(Some(msl::Version::V2_0)).unwrap();
println!(
"{:?}",
compiled.context.fragment.translation_info.entry_point_names
);
}
}

View file

@ -0,0 +1,54 @@
use crate::back::spirv::{NagaSpirvContext, NagaSpirvOptions};
use crate::back::targets::SPIRV;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::naga::NagaReflect;
use naga::back::spv::PipelineOptions;
use naga::valid::{Capabilities, ValidationFlags};
use naga::Module;
impl CompileShader<SPIRV> for NagaReflect {
type Options = NagaSpirvOptions;
type Context = NagaSpirvContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<Vec<u32>, Self::Context>, ShaderCompileError> {
fn write_spv(
module: &Module,
stage: naga::ShaderStage,
version: (u8, u8),
) -> Result<Vec<u32>, ShaderCompileError> {
let mut valid =
naga::valid::Validator::new(ValidationFlags::all(), Capabilities::empty());
let info = valid.validate(&module)?;
let mut options = naga::back::spv::Options::default();
options.lang_version = version;
let spv = naga::back::spv::write_vec(
&module,
&info,
&options,
Some(&PipelineOptions {
shader_stage: stage,
entry_point: "main".to_string(),
}),
)?;
Ok(spv)
}
self.do_lowering(&options.lowering);
let fragment = write_spv(&self.fragment, naga::ShaderStage::Fragment, options.version)?;
let vertex = write_spv(&self.vertex, naga::ShaderStage::Vertex, options.version)?;
Ok(ShaderCompilerOutput {
vertex,
fragment,
context: NagaSpirvContext {
fragment: self.fragment,
vertex: self.vertex,
},
})
}
}

View file

@ -0,0 +1,41 @@
use crate::back::targets::WGSL;
use crate::back::wgsl::NagaWgslContext;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::naga::{NagaLoweringOptions, NagaReflect};
use naga::back::wgsl::WriterFlags;
use naga::valid::{Capabilities, ModuleInfo, ValidationFlags, Validator};
use naga::Module;
impl CompileShader<WGSL> for NagaReflect {
type Options = NagaLoweringOptions;
type Context = NagaWgslContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
fn write_wgsl(module: &Module, info: &ModuleInfo) -> Result<String, ShaderCompileError> {
let wgsl = naga::back::wgsl::write_string(&module, &info, WriterFlags::empty())?;
Ok(wgsl)
}
self.do_lowering(&options);
let mut valid = Validator::new(ValidationFlags::all(), Capabilities::empty());
let vertex_info = valid.validate(&self.vertex)?;
let fragment_info = valid.validate(&self.fragment)?;
let fragment = write_wgsl(&self.fragment, &fragment_info)?;
let vertex = write_wgsl(&self.vertex, &vertex_info)?;
Ok(ShaderCompilerOutput {
vertex,
fragment,
context: NagaWgslContext {
fragment: self.fragment,
vertex: self.vertex,
},
})
}
}

View file

@ -1,13 +1,13 @@
use crate::back::targets::OutputTarget;
use crate::back::{CompilerBackend, FromCompilation};
use crate::error::{ShaderCompileError, ShaderReflectError};
use crate::front::ShaderCompilation;
use crate::front::{ShaderInputCompiler, ShaderReflectObject};
use crate::reflect::semantics::{
Semantic, ShaderSemantics, TextureSemantics, UniformSemantic, UniqueSemantics,
};
use librashader_common::map::FastHashMap;
use librashader_preprocess::{PreprocessError, ShaderSource};
use librashader_presets::{ShaderPassConfig, TextureConfig};
use rustc_hash::FxHashMap;
/// Artifacts of a reflected and compiled shader pass.
///
@ -18,11 +18,12 @@ use rustc_hash::FxHashMap;
/// ```rust
/// #![feature(type_alias_impl_trait)]
/// use librashader_reflect::back::CompileReflectShader;
/// use librashader_reflect::back::targets::SPIRV;
/// use librashader_reflect::front::GlslangCompilation;
/// use librashader_reflect::back::targets::{GLSL, SPIRV};
/// use librashader_reflect::front::SpirvCompilation;
/// use librashader_reflect::reflect::cross::SpirvCross;
/// use librashader_reflect::reflect::presets::ShaderPassArtifact;
///
/// type VulkanPassMeta = ShaderPassArtifact<impl CompileReflectShader<SPIRV, GlslangCompilation>>;
/// type VulkanPassMeta = ShaderPassArtifact<impl CompileReflectShader<SPIRV, SpirvCompilation, SpirvCross>>;
/// ```
///
/// This allows a runtime to not name the backing type of the compiled artifact if not necessary.
@ -35,57 +36,59 @@ impl<T: OutputTarget> CompilePresetTarget for T {}
pub trait CompilePresetTarget: OutputTarget {
/// Compile passes of a shader preset given the applicable
/// shader output target, compilation type, and resulting error.
fn compile_preset_passes<C, E>(
fn compile_preset_passes<I, R, E>(
passes: Vec<ShaderPassConfig>,
textures: &[TextureConfig],
) -> Result<
(
Vec<ShaderPassArtifact<<Self as FromCompilation<C>>::Output>>,
Vec<ShaderPassArtifact<<Self as FromCompilation<I, R>>::Output>>,
ShaderSemantics,
),
E,
>
where
I: ShaderReflectObject,
Self: Sized,
Self: FromCompilation<C>,
C: ShaderCompilation,
Self: FromCompilation<I, R>,
I::Compiler: ShaderInputCompiler<I>,
E: From<PreprocessError>,
E: From<ShaderReflectError>,
E: From<ShaderCompileError>,
{
compile_preset_passes::<Self, C, E>(passes, textures)
compile_preset_passes::<Self, I, R, E>(passes, textures)
}
}
/// Compile passes of a shader preset given the applicable
/// shader output target, compilation type, and resulting error.
fn compile_preset_passes<T, C, E>(
fn compile_preset_passes<T, I, R, E>(
passes: Vec<ShaderPassConfig>,
textures: &[TextureConfig],
) -> Result<
(
Vec<ShaderPassArtifact<<T as FromCompilation<C>>::Output>>,
Vec<ShaderPassArtifact<<T as FromCompilation<I, R>>::Output>>,
ShaderSemantics,
),
E,
>
where
I: ShaderReflectObject,
T: OutputTarget,
T: FromCompilation<C>,
C: ShaderCompilation,
T: FromCompilation<I, R>,
I::Compiler: ShaderInputCompiler<I>,
E: From<PreprocessError>,
E: From<ShaderReflectError>,
E: From<ShaderCompileError>,
{
let mut uniform_semantics: FxHashMap<String, UniformSemantic> = Default::default();
let mut texture_semantics: FxHashMap<String, Semantic<TextureSemantics>> = Default::default();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
let mut texture_semantics: FastHashMap<String, Semantic<TextureSemantics>> = Default::default();
let passes = passes
.into_iter()
.map(|shader| {
let source: ShaderSource = ShaderSource::load(&shader.name)?;
let compiled = C::compile(&source)?;
let compiled = I::Compiler::compile(&source)?;
let reflect = T::from_compilation(compiled)?;
for parameter in source.parameters.values() {
@ -116,8 +119,8 @@ where
/// Insert the available semantics for the input pass config into the provided semantic maps.
fn insert_pass_semantics(
uniform_semantics: &mut FxHashMap<String, UniformSemantic>,
texture_semantics: &mut FxHashMap<String, Semantic<TextureSemantics>>,
uniform_semantics: &mut FastHashMap<String, UniformSemantic>,
texture_semantics: &mut FastHashMap<String, Semantic<TextureSemantics>>,
config: &ShaderPassConfig,
) {
let Some(alias) = &config.alias else {
@ -167,8 +170,8 @@ fn insert_pass_semantics(
/// Insert the available semantics for the input texture config into the provided semantic maps.
fn insert_lut_semantics(
textures: &[TextureConfig],
uniform_semantics: &mut FxHashMap<String, UniformSemantic>,
texture_semantics: &mut FxHashMap<String, Semantic<TextureSemantics>>,
uniform_semantics: &mut FastHashMap<String, UniformSemantic>,
texture_semantics: &mut FastHashMap<String, Semantic<TextureSemantics>>,
) {
for (index, texture) in textures.iter().enumerate() {
texture_semantics.insert(

View file

@ -1,5 +1,5 @@
use bitflags::bitflags;
use rustc_hash::FxHashMap;
use librashader_common::map::FastHashMap;
use std::str::FromStr;
/// The maximum number of bindings allowed in a shader.
@ -40,11 +40,18 @@ pub enum UniqueSemantics {
/// The frame count, possibly with shader-defined modulo.
FrameCount = 3,
// int, frame direction
/// The frame direction.
/// The direction in time where frames are rendered
FrameDirection = 4,
//int, rotation (glUniform1i(uni->rotation, retroarch_get_rotation());)
/// The rotation index (0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg)
Rotation = 5,
/// Total number of subframes.
TotalSubFrames = 6,
/// The current subframe (default 1)
CurrentSubFrame = 7,
/// A user defined float parameter.
// float, user defined parameter, array
FloatParameter = 5,
FloatParameter = 8,
}
impl UniqueSemantics {
@ -64,6 +71,9 @@ impl UniqueSemantics {
UniqueSemantics::FinalViewport => UniformType::Vec4,
UniqueSemantics::FrameCount => UniformType::Unsigned,
UniqueSemantics::FrameDirection => UniformType::Signed,
UniqueSemantics::Rotation => UniformType::Unsigned,
UniqueSemantics::TotalSubFrames => UniformType::Unsigned,
UniqueSemantics::CurrentSubFrame => UniformType::Unsigned,
UniqueSemantics::FloatParameter => UniformType::Float,
}
}
@ -162,6 +172,7 @@ pub struct Semantic<T, I = usize> {
bitflags! {
/// The pipeline stage for which a uniform is bound.
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct BindingStage: u8 {
const NONE = 0b00000000;
const VERTEX = 0b00000001;
@ -169,26 +180,17 @@ bitflags! {
}
}
/// Reflection information for the Uniform Buffer
/// Reflection information for the Uniform Buffer or Push Constant Block
#[derive(Debug)]
pub struct UboReflection {
/// The binding point for this UBO.
pub binding: u32,
/// The size of the UBO buffer. UBO sizes returned by reflection is always aligned to a 16 byte boundary.
pub struct BufferReflection<T> {
/// The binding point for this buffer, if applicable
pub binding: T,
/// The size of the buffer. Buffer sizes returned by reflection is always aligned to a 16 byte boundary.
pub size: u32,
/// The mask indicating for which stages the UBO should be bound.
pub stage_mask: BindingStage,
}
/// Reflection information for the Push Constant Block
#[derive(Debug)]
pub struct PushReflection {
/// The size of the Push Constant range. The size returned by reflection is always aligned to a 16 byte boundary.
pub size: u32,
/// The mask indicating for which stages the Push Constant range should be bound.
pub stage_mask: BindingStage,
}
/// The offset of a uniform member.
///
/// A uniform can be bound to both the UBO, or as a Push Constant.
@ -279,9 +281,9 @@ pub struct TextureBinding {
#[derive(Debug)]
pub struct ShaderReflection {
/// Reflection information about the UBO for this shader.
pub ubo: Option<UboReflection>,
pub ubo: Option<BufferReflection<u32>>,
/// Reflection information about the Push Constant range for this shader.
pub push_constant: Option<PushReflection>,
pub push_constant: Option<BufferReflection<Option<u32>>>,
/// Metadata about the bindings required for this shader.
pub meta: BindingMeta,
}
@ -319,7 +321,7 @@ pub trait TextureSemanticMap {
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>>;
}
impl TextureSemanticMap for FxHashMap<String, UniformSemantic> {
impl TextureSemanticMap for FastHashMap<String, UniformSemantic> {
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
match self.get(name) {
None => {
@ -351,7 +353,7 @@ impl TextureSemanticMap for FxHashMap<String, UniformSemantic> {
}
}
impl TextureSemanticMap for FxHashMap<String, Semantic<TextureSemantics>> {
impl TextureSemanticMap for FastHashMap<String, Semantic<TextureSemantics>> {
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
match self.get(name) {
None => {
@ -361,7 +363,9 @@ impl TextureSemanticMap for FxHashMap<String, Semantic<TextureSemantics>> {
{
if semantics.is_indexed() {
let index = &name[semantics.texture_name().len()..];
let Ok(index) = usize::from_str(index) else {return None};
let Ok(index) = usize::from_str(index) else {
return None;
};
return Some(Semantic {
semantics: *semantics,
index,
@ -386,7 +390,7 @@ pub trait UniqueSemanticMap {
fn get_unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>>;
}
impl UniqueSemanticMap for FxHashMap<String, UniformSemantic> {
impl UniqueSemanticMap for FastHashMap<String, UniformSemantic> {
fn get_unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>> {
match self.get(name) {
// existing uniforms in the semantic map have priority
@ -411,6 +415,18 @@ impl UniqueSemanticMap for FxHashMap<String, UniformSemantic> {
semantics: UniqueSemantics::FrameDirection,
index: (),
}),
"Rotation" => Some(Semantic {
semantics: UniqueSemantics::Rotation,
index: (),
}),
"TotalSubFrames" => Some(Semantic {
semantics: UniqueSemantics::TotalSubFrames,
index: (),
}),
"CurrentSubFrame" => Some(Semantic {
semantics: UniqueSemantics::TotalSubFrames,
index: (),
}),
_ => None,
},
Some(UniformSemantic::Unique(variable)) => Some(*variable),
@ -432,9 +448,9 @@ pub enum UniformSemantic {
#[derive(Debug, Clone)]
pub struct ShaderSemantics {
/// A map of uniform names to filter chain semantics.
pub uniform_semantics: FxHashMap<String, UniformSemantic>,
pub uniform_semantics: FastHashMap<String, UniformSemantic>,
/// A map of texture names to filter chain semantics.
pub texture_semantics: FxHashMap<String, Semantic<TextureSemantics>>,
pub texture_semantics: FastHashMap<String, Semantic<TextureSemantics>>,
}
/// The binding of a uniform after the shader has been linked.
@ -467,11 +483,11 @@ impl From<Semantic<TextureSemantics>> for UniformBinding {
#[derive(Debug, Default)]
pub struct BindingMeta {
/// A map of parameter names to uniform binding metadata.
pub parameter_meta: FxHashMap<String, VariableMeta>,
pub parameter_meta: FastHashMap<String, VariableMeta>,
/// A map of unique semantics to uniform binding metadata.
pub unique_meta: FxHashMap<UniqueSemantics, VariableMeta>,
pub unique_meta: FastHashMap<UniqueSemantics, VariableMeta>,
/// A map of texture semantics to texture binding points.
pub texture_meta: FxHashMap<Semantic<TextureSemantics>, TextureBinding>,
pub texture_meta: FastHashMap<Semantic<TextureSemantics>, TextureBinding>,
/// A map of texture semantics to texture size uniform binding metadata.
pub texture_size_meta: FxHashMap<Semantic<TextureSemantics>, TextureSizeMeta>,
pub texture_size_meta: FastHashMap<Semantic<TextureSemantics>, TextureSizeMeta>,
}

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