├── .github └── workflows │ ├── maven-build.yml │ └── maven-publish.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── pom.xml └── src └── main ├── java ├── de │ └── teragam │ │ └── jfxshader │ │ ├── ImagePoolPolicy.java │ │ ├── JFXShader.java │ │ ├── JFXShaderModule.java │ │ ├── MaterialController.java │ │ ├── ShaderController.java │ │ ├── ShaderDeclaration.java │ │ ├── effect │ │ ├── EffectDependencies.java │ │ ├── EffectPeer.java │ │ ├── EffectRenderer.java │ │ ├── IEffectRenderer.java │ │ ├── InternalEffect.java │ │ ├── OneSamplerEffect.java │ │ ├── ShaderEffect.java │ │ ├── ShaderEffectPeer.java │ │ ├── ShaderEffectPeerConfig.java │ │ ├── TwoSamplerEffect.java │ │ └── internal │ │ │ ├── DefaultEffectRenderer.java │ │ │ ├── EffectRenderTimer.java │ │ │ ├── PPSMultiSamplerPeer.java │ │ │ ├── PolicyBasedImagePool.java │ │ │ ├── RTTTextureHelper.java │ │ │ ├── ShaderEffectBase.java │ │ │ ├── d3d │ │ │ └── D3DRTTextureHelper.java │ │ │ └── es2 │ │ │ ├── ES2RTTextureHelper.java │ │ │ └── GLContext.java │ │ ├── exception │ │ ├── ShaderCreationException.java │ │ ├── ShaderException.java │ │ └── TextureCreationException.java │ │ ├── material │ │ ├── MaterialDependency.java │ │ ├── ShaderMaterial.java │ │ ├── ShaderMaterialPeer.java │ │ └── internal │ │ │ ├── AbstractShaderMaterialPeerRenderer.java │ │ │ ├── BaseMeshHelper.java │ │ │ ├── InternalBasePhongMaterial.java │ │ │ ├── InternalNGBox.java │ │ │ ├── InternalNGCylinder.java │ │ │ ├── InternalNGMeshView.java │ │ │ ├── InternalNGPhongMaterial.java │ │ │ ├── InternalNGSphere.java │ │ │ ├── MeshProxyHelper.java │ │ │ ├── ShaderBaseMesh.java │ │ │ ├── ShaderMaterialBase.java │ │ │ ├── ShaderMeshView.java │ │ │ ├── d3d │ │ │ ├── D3D9Types.java │ │ │ ├── D3DBaseMeshHelper.java │ │ │ ├── D3DShaderMaterialPeerRenderer.java │ │ │ ├── D3DVertexShader.java │ │ │ └── Direct3DDevice9.java │ │ │ └── es2 │ │ │ ├── ES2ShaderMaterialPeerRenderer.java │ │ │ ├── ES2ShaderMeshView.java │ │ │ └── InternalES2BasePhongMaterial.java │ │ ├── renderstate │ │ └── SubResolutionRenderState.java │ │ ├── samples │ │ ├── effects │ │ │ ├── BlendShapes.java │ │ │ ├── BlendShapesEffectPeer.java │ │ │ ├── Pixelate.java │ │ │ ├── PixelateEffectPeer.java │ │ │ ├── ProxyShaderEffect.java │ │ │ ├── ZoomRadialBlur.java │ │ │ └── ZoomRadialBlurEffectPeer.java │ │ └── materials │ │ │ ├── FresnelMaterial.java │ │ │ └── FresnelMaterialPeer.java │ │ └── util │ │ ├── ConstructorInvocationWrapper.java │ │ ├── MethodInvocationWrapper.java │ │ ├── Reflect.java │ │ ├── ReflectMethodCache.java │ │ └── ReflectProxy.java └── module-info.java ├── native-d3d └── IDirect3DDevice9Wrapper.cpp └── resources └── de └── teragam └── jfxshader └── samples ├── effects ├── blendshapes │ ├── blendshapes.frag │ ├── blendshapes.hlsl │ └── blendshapes.obj ├── pixelate │ ├── pixelate.frag │ ├── pixelate.hlsl │ └── pixelate.obj └── zoomradialblur │ ├── zoomradialblur.frag │ ├── zoomradialblur.hlsl │ └── zoomradialblur.obj └── materials └── fresnel ├── fresnel.frag ├── fresnel.ps.hlsl ├── fresnel.ps.obj ├── fresnel.vert ├── fresnel.vs.hlsl └── fresnel.vs.obj /.github/workflows/maven-build.yml: -------------------------------------------------------------------------------- 1 | name: Maven Develop Build 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: windows-latest 11 | permissions: 12 | contents: read 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up JDK 11 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: '11' 20 | distribution: 'temurin' 21 | 22 | - name: Import gpg key 23 | run: | 24 | echo "${{ secrets.GPG_PRIVATE_KEY }}" > gpg_key.asc 25 | gpg --batch --import gpg_key.asc 26 | del gpg_key.asc 27 | 28 | - name: Build natives 29 | run: mvn -B compile -P build-natives-win 30 | 31 | - name: Package jar 32 | run: mvn -B verify "-Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }}" 33 | 34 | - uses: actions/upload-artifact@v3 35 | with: 36 | name: dev-artifact 37 | path: target/*.jar 38 | -------------------------------------------------------------------------------- /.github/workflows/maven-publish.yml: -------------------------------------------------------------------------------- 1 | name: Maven Package 2 | 3 | on: 4 | release: 5 | types: [ created ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: windows-latest 12 | permissions: 13 | contents: read 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Set up JDK 11 18 | uses: actions/setup-java@v3 19 | with: 20 | java-version: '11' 21 | distribution: 'temurin' 22 | server-id: ossrh 23 | server-username: OSSRH_USERNAME 24 | server-password: OSSRH_TOKEN 25 | settings-path: ${{ github.workspace }} 26 | 27 | - name: Import gpg key 28 | run: | 29 | echo "${{ secrets.GPG_PRIVATE_KEY }}" > gpg_key.asc 30 | gpg --batch --import gpg_key.asc 31 | del gpg_key.asc 32 | 33 | - name: Build with Maven 34 | run: mvn -B compile -P build-natives-win 35 | 36 | - name: Publish to Maven Central 37 | run: mvn -B deploy -s ${{ github.workspace }}/settings.xml "-Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }}" 38 | env: 39 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 40 | OSSRH_TOKEN: ${{ secrets.OSSRH_TOKEN }} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ 2 | /.idea 3 | /target 4 | # Compiled class file 5 | *.class 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.nar 19 | *.ear 20 | *.zip 21 | *.tar.gz 22 | *.rar 23 | 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # JFXShader Changelog 2 | 3 | All notable changes to this project will be documented here. 4 | 5 | ## v1.3.1 - 2024-01-27 6 | 7 | ### Added 8 | 9 | - Added a [Demo Page](https://github.com/Teragam/JFXShader/wiki/Demo-Effects) to the wiki that showcases more advanced effects and their breakdowns using the library. 10 | - Validated support for custom JavaFX builds that enable the OpenGL support on Windows. Both 2D and 3D effects are supported. 11 | - Added initial support for multiple render targets in DirectX9 using the [Direct3DDevice9](src/main/java/de/teragam/jfxshader/material/internal/d3d/Direct3DDevice9.java). A sample effect will be added in a future release. 12 | 13 | ### Fixed 14 | 15 | - Fixed macOS support for existing shader samples. 16 | - Fixed RTTexture creation for the OpenGL backend. 17 | - Fixed ShaderMaterial error when using cached 3D shapes in DirectX9. 18 | 19 | ## v1.3.0 - 2023-07-27 20 | 21 | ### Added 22 | 23 | - The library is now available on Maven Central. 24 | 25 | ### Changed 26 | 27 | - Switched license from `GPL-3.0` to `Apache-2.0` to allow for more flexible usage. 28 | - Improved DPI scaling for existing sample effects. 29 | 30 | ## v1.2.0 - 2023-06-07 31 | 32 | ### Added 33 | 34 | - Added Java module support. 35 | - The library can now be used with modular and non-modular JavaFX applications. 36 | - For modular JavaFX applications, `JFXShaderModule.setup()` must be called before using any other of the library's 37 | classes. 38 | - Added more sample effects: 39 | - [Pixelate](src/main/java/de/teragam/jfxshader/samples/effects/Pixelate.java) 40 | - [ZoomRadialBlur](src/main/java/de/teragam/jfxshader/samples/effects/ZoomRadialBlur.java) 41 | - [ProxyShaderEffect](src/main/java/de/teragam/jfxshader/samples/effects/ProxyShaderEffect.java) 42 | - Added continuous rendering option to ShaderEffects. 43 | - EffectPeers can now invalidate their shaders to allow for a more dynamic shader pipeline. 44 | - Added option to create a dedicated EffectPeer for every ShaderEffect instance by setting the default `singleton` 45 | option to `false`. 46 | 47 | ### Changed 48 | 49 | - To support Java modules, ShaderEffects now have `getFXEffect()` to return the compatible JavaFX `Effect` that should 50 | be used. (Analogue to `getFXMaterial()` for ShaderMaterials.) 51 | - EffectPeers can now be static and non-static member classes of their ShaderEffects. 52 | 53 | ## v1.0.0 - 2023-04-30 54 | 55 | ### Added 56 | 57 | - Added support for custom 3D shader materials. 58 | - Applicable to any `Shape3D` by using `setMaterial`. 59 | - Custom parameters and textures can be set and used in the shader. 60 | - Pixel shaders as well as vertex shaders are supported. 61 | - Added [FresnelMaterial](src/main/java/de/teragam/jfxshader/samples/materials/FresnelMaterial.java) as an 62 | example for a custom 3D shader material. 63 | 64 | ### Changed 65 | 66 | - Restructured classes and packages. 67 | - Reduced reliability on internal JavaFX package-private classes for future modularity support. 68 | 69 | ## v0.8.0 - 2022-12-30 70 | 71 | ### Added 72 | 73 | - Shaders can now use up to 16 bound textures simultaneously. 74 | - Some refactoring and QoL improvements to ease the development of 75 | custom [IEffectRenderer](src/main/java/de/teragam/jfxshader/effect/IEffectRenderer.java). 76 | - The generated texture coordinates for the shader input textures can now be accessed in 77 | the [ShaderEffectPeer](src/main/java/de/teragam/jfxshader/effect/ShaderEffectPeer.java). This allows the shaders to 78 | compensate for the varying texture coordinates caused by JavaFX's internal texture pooling. 79 | - The `ImagePoolPolicy.QUANTIZED` option has been added to allow better VRAM usage while ensuring that the target 80 | texture dimensions do not fluctuate between frames. 81 | - Added option to invert the mask in 82 | the [BlendShapes](src/main/java/de/teragam/jfxshader/samples/effects/BlendShapesEffectPeer.java) example. 83 | 84 | ### Fixed 85 | 86 | - Fixed an issue that did not allow the use of more than 2 textures in a shader. 87 | 88 | ## v0.7.0 - 2022-11-28 89 | 90 | ### Added 91 | 92 | - Added support to allow shaders to render to a texture with a custom pixel format. Previously, shaders could only 93 | render to a texture with the pixel format *INT_ARGB_PRE*. 94 | - The output pixel format can now be specified in the EffectPeer interface along with other options, such as the wrap 95 | mode and whether mipmaps should be generated. 96 | Note: For OpenGL, JavaFX uses a deprecated method to generate mipmaps. Therefore, mipmaps are only generated if the 97 | version of OpenGL is below 3.1. 98 | - Shaders can now use up to 4 texture samplers. Previously, shaders could only use 2 textures. Unfortunately, any 99 | textures above 2 do not have dedicated texture coordinates, due to limitations of JavaFX. 100 | - Added support for shaders that use their previous output as an input texture. An example of this will be added in a 101 | future release. 102 | - Added basic support for DPI scaling. JavaFX accomplishes this by applying a scale transform. However, this does not 103 | work for shaders that rely on positional information, such as the *Lighting* Effect. For now, the shaders can load the 104 | DPI scale as a parameter and compensate for it accordingly (see 105 | the [BlendShapes](src/main/java/de/teragam/jfxshader/samples/effects/BlendShapesEffectPeer.java) 106 | EffectPeer for an 107 | example). 108 | 109 | ### Changed 110 | 111 | - The Constructor of ShaderEffectPeer now takes a ShaderEffectPeerConfig instead of a FilterContext. 112 | 113 | ## v0.5.0 - 2022-10-25 114 | 115 | ### Added 116 | 117 | - Initial release of the JFXShader library. 118 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # JNI header files 2 | JAVA_HOME = %JAVA_HOME% 3 | JNI_INCLUDE = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/win32 4 | 5 | # Additional include paths 6 | INCLUDES = -Itarget/generated-headers 7 | 8 | # Source files 9 | SOURCES = IDirect3DDevice9Wrapper.cpp 10 | 11 | # Object files 12 | OBJECTS = $(addprefix target/build/, $(SOURCES:.cpp=.o)) 13 | 14 | LIBRARY = src/main/resources/jfxshader$(if $(VERSION),_$(VERSION)).dll 15 | 16 | all: create-dirs $(LIBRARY) 17 | 18 | $(LIBRARY): $(OBJECTS) 19 | $(CXX) -shared -o $@ $^ -Wl,--add-stdcall-alias 20 | 21 | target/build/%.o: src/main/native-d3d/%.cpp 22 | $(CXX) $(CFLAGS) -c $(JNI_INCLUDE) $(INCLUDES) -o $@ $< 23 | 24 | clean: 25 | rm -f target/build/$(OBJECTS) $(LIBRARY) 26 | 27 | create-dirs: 28 | mkdir -p target/build 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Maven Central](https://img.shields.io/maven-central/v/de.teragam/jfxshader.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22de.teragam%22%20AND%20a:%22jfxshader%22) 2 | [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 3 | 4 | # JFXShader 5 | 6 | Allows custom effect shaders in JavaFX using OpenGL (GLSL) or DirectX (HLSL). 7 | 8 | ## Usage 9 | 10 | Examples can be found in the [wiki](https://github.com/Teragam/JFXShader/wiki) and in the [samples](src/main/java/de/teragam/jfxshader/samples) package. 11 | 12 | ### 2D shader effects 13 | 14 | A custom shader can be applied to any JavaFX node hierarchy by using `setEffect`: 15 | ```java 16 | StackPane pane = new StackPane(getOtherContent()); 17 | 18 | // Instead of using existing JavaFX effects like SepiaTone or ColorAdjust, 19 | // we can apply our own effect with custom shaders: 20 | MyCustomShaderEffect effect = new MyCustomShaderEffect(); 21 | pane.setEffect(effect.getFXEffect()); 22 | // Custom parameters can be set and used in the shader: 23 | effect.setMyCustomParameter(2.0); 24 | ``` 25 | 26 | Instructions on how to implement a custom shader effect can be found in 27 | the [wiki](https://github.com/Teragam/JFXShader/wiki/Examples). 28 | 29 | ### 3D shader materials 30 | 31 | A custom material can be applied to any Shape3D by using `setMaterial`: 32 | ```java 33 | Sphere sphere = new Sphere(100); 34 | 35 | // Instead of being limited to PhongMaterial, 36 | // we can apply our own material with custom shaders: 37 | MyCustomShaderMaterial material = new MyCustomShaderMaterial(); 38 | sphere.setMaterial(material.getFXMaterial()); 39 | // Custom parameters or textures can be set and used in the shader: 40 | material.setMyCustomParameter(3.0); 41 | material.setMyCustomTexture(new Image("myCustomTexture.png")); 42 | ``` 43 | 44 | ## Maven 45 | 46 | To include JFXShader in a Maven project, add the following dependency to the pom.xml: 47 | 48 | ```xml 49 | 50 | de.teragam 51 | jfxshader 52 | 1.3.1 53 | 54 | ``` 55 | 56 | ## Requirements 57 | 58 | - Java 11 or higher 59 | - JavaFX version 18 or higher 60 | 61 | ## Limitations 62 | 63 | This library is bound to the restrictions of the JavaFX effects system. The following limitations apply: 64 | 65 | - Only fragment/pixel shaders are supported. The vertex shaders of 2D effects cannot be overwritten. However, custom vertex shaders can be used for 3D materials. 66 | - The fragment/pixel shaders only support one active render target (output texture). 67 | - To use OpenGL shaders on Windows, a custom JavaFX build with OpenGL support is required. The official JavaFX builds only support DirectX shaders on Windows. 68 | - For DirectX, only ps_3_0 shaders are supported. 69 | - JavaFX requires DirectX shaders to be compiled with the `fxc` compiler. The following command can be used to compile 70 | HLSL shaders: `fxc.exe /nologo /T ps_3_0 /Fo .\shader.obj .\shader.hlsl` 71 | - On some systems (i.e. VMs), it is necessary to enable the GPU usage by setting the `prism.forceGPU` system property 72 | to `true`. 73 | - Due to dirty region optimizations, JavaFX may apply the shader effect only to the dirty region of the node. This can 74 | lead to artifacts. To disable dirty region optimizations, set the `prism.dirtyopts` system property to `false`. 75 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/ImagePoolPolicy.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader; 2 | 3 | /** 4 | * Defines the texture selection policy for the image pool. 5 | */ 6 | public enum ImagePoolPolicy { 7 | 8 | /** 9 | * The texture pool only returns textures that are exactly the same size as the requested texture. 10 | * If no texture is available, a new texture is created. 11 | * This policy will result in larger VRAM usage because more textures are created. 12 | *

13 | * This policy cannot be applied if one of the following conditions apply: 14 | *

20 | */ 21 | EXACT(1, false), 22 | 23 | /** 24 | * The texture pool will return textures that are at least as large as the requested texture. 25 | * In contrast to {@link #LENIENT}, this policy assures that the texture dimensions will not fluctuate between frames, as the pool will only return 26 | * textures with dimensions that exactly match a quantized version of the requested dimensions. 27 | */ 28 | QUANTIZED(32, false), 29 | 30 | /** 31 | * The texture pool will return textures that are at least as large as the requested texture. 32 | * The texture dimensions may fluctuate between frames because the pool will select textures that loosely match the requested dimensions. 33 | * This may cause problems for shaders that rely on the texture size being consistent between multiple frames. 34 | * This policy will result in smaller VRAM usage because more textures are reused and fewer textures are created. 35 | * JavaFX uses this policy for its own textures. 36 | */ 37 | LENIENT(32, true); 38 | 39 | private final int quantization; 40 | private final boolean approximateMatch; 41 | 42 | ImagePoolPolicy(int quantization, boolean approximateMatch) { 43 | this.quantization = quantization; 44 | this.approximateMatch = approximateMatch; 45 | } 46 | 47 | public int getQuantization() { 48 | return this.quantization; 49 | } 50 | 51 | public boolean isApproximateMatch() { 52 | return this.approximateMatch; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/JFXShader.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader; 2 | 3 | import com.sun.prism.ps.Shader; 4 | 5 | import de.teragam.jfxshader.util.ReflectProxy; 6 | 7 | public interface JFXShader extends Shader, ReflectProxy { 8 | 9 | void setMatrix(String name, float[] buf, int vector4fCount); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/JFXShaderModule.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.atomic.AtomicBoolean; 5 | 6 | import de.teragam.jfxshader.exception.ShaderException; 7 | import de.teragam.jfxshader.util.Reflect; 8 | 9 | public final class JFXShaderModule { 10 | 11 | public static final String VERSION = "1.3.1"; 12 | 13 | private static final AtomicBoolean initialized = new AtomicBoolean(); 14 | 15 | private JFXShaderModule() {} 16 | 17 | /** 18 | * Opens internal JavaFX packages to the JFXShader module. 19 | * Has no effect for non-modular JavaFX applications. 20 | */ 21 | public static void setup() { 22 | if (JFXShaderModule.initialized.getAndSet(true) || ModuleLayer.boot().findModule("javafx.graphics").isEmpty()) { 23 | return; 24 | } 25 | JFXShaderModule.provideModuleAccess(JFXShaderModule.class.getModule()); 26 | } 27 | 28 | /** 29 | * Opens internal JavaFX packages to the provided module. 30 | * 31 | * @param module the module to open the packages to 32 | */ 33 | public static void provideModuleAccess(Module module) { 34 | JFXShaderModule.getGraphicsPackages().forEach(pkg -> Reflect.addOpens(pkg, "javafx.graphics", module)); 35 | JFXShaderModule.getOptionalGraphicsPackages().forEach(pkg -> { 36 | try { 37 | Reflect.addOpens(pkg, "javafx.graphics", module); 38 | } catch (ShaderException e) { 39 | // ignore 40 | } 41 | }); 42 | JFXShaderModule.getBasePackages().forEach(pkg -> Reflect.addOpens(pkg, "javafx.base", module)); 43 | } 44 | 45 | private static List getGraphicsPackages() { 46 | return List.of("com.sun.javafx.effect", "com.sun.javafx.geom", "com.sun.javafx.geom.transform", "com.sun.javafx.scene", 47 | "com.sun.scenario.effect.impl.state", "com.sun.scenario.effect", "com.sun.prism", "com.sun.prism.impl", "com.sun.scenario.effect.impl.prism", 48 | "com.sun.prism.ps", "com.sun.glass.ui", "com.sun.glass.utils", "com.sun.javafx.scene.shape", "com.sun.javafx.sg.prism", "com.sun.javafx.util", 49 | "com.sun.prism.impl.ps", "com.sun.scenario.effect.impl", "com.sun.scenario.effect.impl.prism.ps", "com.sun.javafx.scene.paint", 50 | "com.sun.javafx.tk", "com.sun.javafx.beans.event", "javafx.scene.effect", "javafx.scene.paint", "javafx.scene"); 51 | } 52 | 53 | private static List getOptionalGraphicsPackages() { 54 | return List.of("com.sun.prism.es2", "com.sun.prism.d3d"); 55 | } 56 | 57 | private static List getBasePackages() { 58 | return List.of("com.sun.javafx"); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/MaterialController.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader; 2 | 3 | import java.lang.reflect.Proxy; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.function.Supplier; 7 | 8 | import javafx.scene.paint.PhongMaterial; 9 | import javafx.scene.shape.MeshView; 10 | import javafx.scene.shape.Shape3D; 11 | 12 | import com.sun.glass.utils.NativeLibLoader; 13 | import com.sun.javafx.PlatformUtil; 14 | import com.sun.javafx.scene.paint.MaterialHelper; 15 | import com.sun.javafx.scene.shape.BoxHelper; 16 | import com.sun.javafx.scene.shape.CylinderHelper; 17 | import com.sun.javafx.scene.shape.MeshViewHelper; 18 | import com.sun.javafx.scene.shape.Shape3DHelper; 19 | import com.sun.javafx.scene.shape.SphereHelper; 20 | import com.sun.javafx.sg.prism.NGShape3D; 21 | import com.sun.javafx.util.Utils; 22 | import com.sun.prism.ResourceFactory; 23 | 24 | import de.teragam.jfxshader.effect.internal.d3d.D3DRTTextureHelper; 25 | import de.teragam.jfxshader.material.MaterialDependency; 26 | import de.teragam.jfxshader.material.ShaderMaterial; 27 | import de.teragam.jfxshader.material.ShaderMaterialPeer; 28 | import de.teragam.jfxshader.material.internal.InternalNGBox; 29 | import de.teragam.jfxshader.material.internal.InternalNGCylinder; 30 | import de.teragam.jfxshader.material.internal.InternalNGMeshView; 31 | import de.teragam.jfxshader.material.internal.InternalNGSphere; 32 | import de.teragam.jfxshader.material.internal.ShaderMaterialBase; 33 | import de.teragam.jfxshader.material.internal.d3d.Direct3DDevice9; 34 | import de.teragam.jfxshader.util.Reflect; 35 | 36 | public final class MaterialController { 37 | 38 | private static final HashMap, ShaderMaterialPeer> MATERIAL_PEER_MAP = new HashMap<>(); 39 | private static final Map D3D_DEVICE_MAP = new HashMap<>(); 40 | 41 | private MaterialController() {} 42 | 43 | public static Direct3DDevice9 getD3DDevice(ResourceFactory resourceFactory) { 44 | return D3D_DEVICE_MAP.computeIfAbsent(D3DRTTextureHelper.getDevice(resourceFactory), Direct3DDevice9::new); 45 | } 46 | 47 | public static ShaderMaterialPeer getPeer(ShaderMaterial material) { 48 | return MATERIAL_PEER_MAP.computeIfAbsent(material.getClass(), MaterialController::createPeer); 49 | } 50 | 51 | private static ShaderMaterialPeer createPeer(Class material) { 52 | if (material.isAnnotationPresent(MaterialDependency.class)) { 53 | final Class> peerClass = material.getAnnotation(MaterialDependency.class).value(); 54 | return Reflect.on(peerClass).constructor().create(); 55 | } else { 56 | throw new IllegalArgumentException(String.format("%s is not annotated with %s", material, MaterialDependency.class)); 57 | } 58 | } 59 | 60 | /** 61 | * Replaces the internal {@link Shape3DHelper} accessors with custom accessors that support custom 3D shaders. 62 | * To allow for custom shaders on default {@link MeshView} instances, this injection must be performed before any {@link Shape3D} is rendered. 63 | * Otherwise, only {@link Shape3D} instances created after this injection will support custom shaders. 64 | */ 65 | public static void setup3D() { 66 | if (PlatformUtil.isWindows()) { 67 | NativeLibLoader.loadLibrary("jfxshader_" + JFXShaderModule.VERSION); 68 | } 69 | MaterialController.injectMaterialAccessor(); 70 | MaterialController.injectShape3DAccessor(MeshViewHelper.class, "meshViewAccessor", InternalNGMeshView::new); 71 | MaterialController.injectShape3DAccessor(SphereHelper.class, "sphereAccessor", InternalNGSphere::new); 72 | MaterialController.injectShape3DAccessor(BoxHelper.class, "boxAccessor", InternalNGBox::new); 73 | MaterialController.injectShape3DAccessor(CylinderHelper.class, "cylinderAccessor", InternalNGCylinder::new); 74 | } 75 | 76 | private static void injectMaterialAccessor() { 77 | Utils.forceInit(MaterialHelper.class); 78 | final MaterialHelper.MaterialAccessor accessor = Reflect.on(MaterialHelper.class).getFieldValue("materialAccessor", null); 79 | if (Proxy.isProxyClass(accessor.getClass())) { 80 | return; 81 | } 82 | final Object proxy = Proxy.newProxyInstance(accessor.getClass().getClassLoader(), accessor.getClass().getInterfaces(), (p, method, args) -> { 83 | if ("getNGMaterial".equals(method.getName())) { 84 | if (Reflect.on(PhongMaterial.class).getFieldValue("peer", args[0]) == null && args[0] instanceof ShaderMaterialBase) { 85 | Reflect.on(PhongMaterial.class).setFieldValue("peer", args[0], ((ShaderMaterialBase) args[0]).getInternalNGMaterial()); 86 | } 87 | } else if ("updatePG".equals(method.getName()) && (args[0] instanceof ShaderMaterialBase)) { 88 | ((ShaderMaterialBase) args[0]).updateMaterial(); 89 | } 90 | return method.invoke(accessor, args); 91 | }); 92 | Reflect.on(MaterialHelper.class).setFieldValue("materialAccessor", null, proxy); 93 | } 94 | 95 | private static void injectShape3DAccessor(Class helper, String accessorField, Supplier shapeClassSupplier) { 96 | Utils.forceInit(helper); 97 | final Object accessor = Reflect.on(helper).getFieldValue(accessorField, null); 98 | if (Proxy.isProxyClass(accessor.getClass())) { 99 | return; 100 | } 101 | final Object proxy = Proxy.newProxyInstance(accessor.getClass().getClassLoader(), accessor.getClass().getInterfaces(), (p, method, args) -> { 102 | if ("doCreatePeer".equals(method.getName())) { 103 | return shapeClassSupplier.get(); 104 | } else { 105 | return method.invoke(accessor, args); 106 | } 107 | }); 108 | Reflect.on(helper).setFieldValue(accessorField, null, proxy); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/ShaderDeclaration.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader; 2 | 3 | import java.io.InputStream; 4 | import java.util.Map; 5 | 6 | public final class ShaderDeclaration { 7 | private final Map samplers; 8 | private final Map params; 9 | private final InputStream es2Source; 10 | private final InputStream d3dSource; 11 | 12 | public ShaderDeclaration(Map samplers, Map params, 13 | InputStream es2Source, InputStream d3dSource) { 14 | this.samplers = samplers; 15 | this.params = params; 16 | this.es2Source = es2Source; 17 | this.d3dSource = d3dSource; 18 | } 19 | 20 | public Map samplers() { 21 | return this.samplers; 22 | } 23 | 24 | public Map params() { 25 | return this.params; 26 | } 27 | 28 | public InputStream es2Source() { 29 | return this.es2Source; 30 | } 31 | 32 | public InputStream d3dSource() { 33 | return this.d3dSource; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/EffectDependencies.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Documented 10 | @Target(ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface EffectDependencies { 13 | Class>[] value(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/EffectPeer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import com.sun.prism.PixelFormat; 10 | import com.sun.prism.Texture; 11 | 12 | import de.teragam.jfxshader.ImagePoolPolicy; 13 | 14 | @Documented 15 | @Target(ElementType.TYPE) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface EffectPeer { 18 | 19 | /** 20 | * @return The unique name of the peer. 21 | */ 22 | String value(); 23 | 24 | /** 25 | * @return The pixel format of the target texture. 26 | * Defaults to {@link PixelFormat#INT_ARGB_PRE}. 27 | */ 28 | PixelFormat targetFormat() default PixelFormat.INT_ARGB_PRE; 29 | 30 | /** 31 | * @return The wrap mode of the target texture. 32 | * Defaults to {@link Texture.WrapMode#CLAMP_TO_ZERO}. 33 | */ 34 | Texture.WrapMode targetWrapMode() default Texture.WrapMode.CLAMP_TO_ZERO; 35 | 36 | /** 37 | * Enables mipmaps for the target texture. 38 | * Defaults to {@code false}. 39 | *

40 | * Note: For Opengl, due to the use of deprecated functions, mipmaps are only generated if the 41 | * version of OpenGL is below 3.1. 42 | * 43 | * @return Whether mipmaps should be generated for the target texture. 44 | */ 45 | boolean targetMipmaps() default false; 46 | 47 | /** 48 | * @return The image pool policy for the target texture. 49 | * Defaults to {@link ImagePoolPolicy#LENIENT}. 50 | * @see ImagePoolPolicy 51 | */ 52 | ImagePoolPolicy targetPoolPolicy() default ImagePoolPolicy.LENIENT; 53 | 54 | /** 55 | * @return Whether the peer is a singleton and should be reused for all instances of the effect. 56 | * Defaults to {@code true}. 57 | */ 58 | boolean singleton() default true; 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/EffectRenderer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Inherited; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Inherited 11 | @Documented 12 | @Target(ElementType.TYPE) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface EffectRenderer { 15 | Class value(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/IEffectRenderer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect; 2 | 3 | import com.sun.javafx.geom.Rectangle; 4 | import com.sun.javafx.geom.transform.BaseTransform; 5 | import com.sun.scenario.effect.FilterContext; 6 | import com.sun.scenario.effect.ImageData; 7 | import com.sun.scenario.effect.impl.state.RenderState; 8 | 9 | public interface IEffectRenderer { 10 | 11 | ImageData render(InternalEffect effect, FilterContext fctx, BaseTransform transform, Rectangle outputClip, RenderState rstate, ImageData... inputs); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/InternalEffect.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | import com.sun.javafx.geom.Point2D; 7 | import com.sun.javafx.geom.Rectangle; 8 | import com.sun.javafx.geom.transform.BaseTransform; 9 | import com.sun.scenario.effect.Blend; 10 | import com.sun.scenario.effect.Effect; 11 | import com.sun.scenario.effect.FilterContext; 12 | import com.sun.scenario.effect.ImageData; 13 | import com.sun.scenario.effect.impl.EffectPeer; 14 | import com.sun.scenario.effect.impl.state.RenderState; 15 | 16 | import de.teragam.jfxshader.ShaderController; 17 | import de.teragam.jfxshader.effect.internal.ShaderEffectBase; 18 | import de.teragam.jfxshader.util.Reflect; 19 | 20 | public class InternalEffect extends Blend { 21 | 22 | private final int maxInputs; 23 | private final ShaderEffect effect; 24 | private final Map> peerMap; 25 | 26 | public InternalEffect(ShaderEffect effect, int inputs) { 27 | super(Mode.SRC_OVER, null, null); 28 | this.effect = effect; 29 | this.peerMap = new LinkedHashMap<>(); 30 | this.maxInputs = inputs; 31 | for (int i = 0; i < inputs; i++) { 32 | this.setInput(i, null); 33 | } 34 | } 35 | 36 | @Override 37 | public ImageData filter(FilterContext fctx, BaseTransform transform, Rectangle outputClip, Object renderHelper, Effect defaultInput) { 38 | ShaderController.register(fctx, this.effect); 39 | return super.filter(fctx, transform, outputClip, renderHelper, defaultInput); 40 | } 41 | 42 | @Override 43 | public ImageData filterImageDatas(FilterContext fctx, BaseTransform transform, Rectangle outputClip, RenderState rstate, ImageData... inputs) { 44 | return ShaderController.getEffectRenderer(this.getEffect()).render(this, fctx, transform, outputClip, rstate, inputs); 45 | } 46 | 47 | public ShaderEffect getEffect() { 48 | return this.effect; 49 | } 50 | 51 | @Override 52 | public void setInput(int index, Effect input) { 53 | if (index < this.maxInputs) { 54 | super.setInput(index, input); 55 | } 56 | } 57 | 58 | @Override 59 | public RenderState getRenderState(FilterContext fctx, BaseTransform transform, Rectangle outputClip, Object renderHelper, Effect defaultInput) { 60 | return this.effect.getRenderState(); 61 | } 62 | 63 | @Override 64 | public boolean reducesOpaquePixels() { 65 | boolean allReduces = true; 66 | for (final Effect input : this.getInputs()) { 67 | if (input == null || !input.reducesOpaquePixels()) { 68 | allReduces = false; 69 | break; 70 | } 71 | } 72 | return allReduces; 73 | } 74 | 75 | @Override 76 | public Point2D transform(Point2D p, Effect defaultInput) { 77 | final Effect input = Reflect.on(Effect.class).method("getDefaultedInput", int.class, Effect.class).invoke(this, 0, defaultInput); 78 | return input.transform(p, defaultInput); 79 | } 80 | 81 | @Override 82 | public Point2D untransform(Point2D p, Effect defaultInput) { 83 | final Effect input = Reflect.on(Effect.class).method("getDefaultedInput", int.class, Effect.class).invoke(this, 0, defaultInput); 84 | return input.untransform(p, defaultInput); 85 | } 86 | 87 | @Override 88 | public void setBottomInput(Effect bottomInput) { 89 | // no-op 90 | } 91 | 92 | @Override 93 | public void setTopInput(Effect topInput) { 94 | // no-op 95 | } 96 | 97 | /** 98 | * This override is necessary to detect shader updates, because {@link javafx.scene.effect.Effect#update() Effect.update()} cannot be overridden when 99 | * using the java module system. 100 | *

101 | * setMode() is called by {@link javafx.scene.effect.Blend#update() Blend.update()}, which is the chosen parent of {@link ShaderEffectBase}. 102 | */ 103 | @Override 104 | public void setMode(Mode mode) { 105 | if (this.effect != null) { 106 | ((ShaderEffectBase) this.effect.getFXEffect()).updateInputs(); 107 | } 108 | } 109 | 110 | public Map> getPeerMap() { 111 | return this.peerMap; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/OneSamplerEffect.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect; 2 | 3 | import javafx.beans.property.ObjectProperty; 4 | import javafx.scene.effect.Effect; 5 | 6 | public abstract class OneSamplerEffect extends ShaderEffect { 7 | protected OneSamplerEffect() { 8 | super(1); 9 | } 10 | 11 | public void setInput(Effect value) { 12 | this.inputProperty().set(value); 13 | } 14 | 15 | public Effect getInput() { 16 | return this.inputProperty().get(); 17 | } 18 | 19 | public ObjectProperty inputProperty() { 20 | return super.getInputProperty(0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/ShaderEffect.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect; 2 | 3 | import javafx.beans.property.BooleanProperty; 4 | import javafx.beans.property.DoubleProperty; 5 | import javafx.beans.property.IntegerProperty; 6 | import javafx.beans.property.ObjectProperty; 7 | import javafx.beans.property.SimpleBooleanProperty; 8 | import javafx.beans.property.SimpleDoubleProperty; 9 | import javafx.beans.property.SimpleIntegerProperty; 10 | import javafx.beans.property.SimpleObjectProperty; 11 | import javafx.scene.effect.Effect; 12 | 13 | import com.sun.scenario.effect.impl.state.RenderState; 14 | 15 | import de.teragam.jfxshader.ShaderController; 16 | import de.teragam.jfxshader.effect.internal.DefaultEffectRenderer; 17 | import de.teragam.jfxshader.effect.internal.ShaderEffectBase; 18 | 19 | @EffectRenderer(DefaultEffectRenderer.class) 20 | public abstract class ShaderEffect { 21 | 22 | private final ShaderEffectBase effectBase; 23 | private final int inputs; 24 | 25 | protected ShaderEffect(int inputs) { 26 | ShaderController.injectEffectAccessor(); 27 | this.inputs = inputs; 28 | this.effectBase = new ShaderEffectBase(this, inputs); 29 | } 30 | 31 | public Effect getFXEffect() { 32 | return this.effectBase; 33 | } 34 | 35 | public ShaderEffect copy() { 36 | throw new UnsupportedOperationException("No copy functionality specified"); 37 | } 38 | 39 | public void markDirty() { 40 | this.effectBase.markDirty(); 41 | } 42 | 43 | public int getInputs() { 44 | return this.inputs; 45 | } 46 | 47 | public RenderState getRenderState() { 48 | return RenderState.RenderSpaceRenderState; 49 | } 50 | 51 | protected ObjectProperty getInputProperty(int index) { 52 | if (this.inputs > 0 && index == 0) { 53 | return this.effectBase.topInputProperty(); 54 | } else if (this.inputs > 1 && index == 1) { 55 | return this.effectBase.bottomInputProperty(); 56 | } else { 57 | throw new IllegalArgumentException(String.format("Only indexes 0 to %d are supported. Requested was %d.", this.inputs - 1, index)); 58 | } 59 | } 60 | 61 | protected void setContinuousRendering(boolean continuousRendering) { 62 | this.effectBase.setContinuousRendering(continuousRendering); 63 | } 64 | 65 | protected DoubleProperty createEffectDoubleProperty(double value, String name) { 66 | return new SimpleDoubleProperty(ShaderEffect.this, name, value) { 67 | 68 | @Override 69 | public void invalidated() { 70 | ShaderEffect.this.markDirty(); 71 | } 72 | 73 | }; 74 | } 75 | 76 | protected IntegerProperty createEffectIntegerProperty(int value, String name) { 77 | return new SimpleIntegerProperty(ShaderEffect.this, name, value) { 78 | 79 | @Override 80 | public void invalidated() { 81 | ShaderEffect.this.markDirty(); 82 | } 83 | 84 | }; 85 | } 86 | 87 | protected BooleanProperty createEffectBooleanProperty(boolean value, String name) { 88 | return new SimpleBooleanProperty(ShaderEffect.this, name, value) { 89 | 90 | @Override 91 | public void invalidated() { 92 | ShaderEffect.this.markDirty(); 93 | } 94 | 95 | }; 96 | } 97 | 98 | protected ObjectProperty createEffectObjectProperty(T value, String name) { 99 | return new SimpleObjectProperty<>(ShaderEffect.this, name, value) { 100 | 101 | @Override 102 | public void invalidated() { 103 | ShaderEffect.this.markDirty(); 104 | } 105 | 106 | }; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/ShaderEffectPeer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect; 2 | 3 | import com.sun.scenario.effect.impl.state.RenderState; 4 | 5 | import de.teragam.jfxshader.effect.internal.PPSMultiSamplerPeer; 6 | 7 | public abstract class ShaderEffectPeer extends PPSMultiSamplerPeer { 8 | 9 | protected ShaderEffectPeer(ShaderEffectPeerConfig config) { 10 | super(config); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/ShaderEffectPeerConfig.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect; 2 | 3 | import com.sun.prism.PixelFormat; 4 | import com.sun.prism.Texture; 5 | import com.sun.scenario.effect.FilterContext; 6 | import com.sun.scenario.effect.impl.Renderer; 7 | 8 | import de.teragam.jfxshader.ImagePoolPolicy; 9 | 10 | public final class ShaderEffectPeerConfig { 11 | 12 | private final FilterContext filterContext; 13 | private final Renderer renderer; 14 | private final String shaderName; 15 | private final PixelFormat targetFormat; 16 | private final Texture.WrapMode targetWrapMode; 17 | private final boolean targetMipmaps; 18 | private final ImagePoolPolicy targetPoolPolicy; 19 | 20 | public ShaderEffectPeerConfig(FilterContext filterContext, Renderer renderer, String shaderName, PixelFormat targetFormat, Texture.WrapMode targetWrapMode, 21 | boolean targetMipmaps, ImagePoolPolicy targetPoolPolicy) { 22 | this.filterContext = filterContext; 23 | this.renderer = renderer; 24 | this.shaderName = shaderName; 25 | this.targetFormat = targetFormat; 26 | this.targetWrapMode = targetWrapMode; 27 | this.targetMipmaps = targetMipmaps; 28 | this.targetPoolPolicy = targetPoolPolicy; 29 | } 30 | 31 | public FilterContext getFilterContext() { 32 | return this.filterContext; 33 | } 34 | 35 | public Renderer getRenderer() { 36 | return this.renderer; 37 | } 38 | 39 | public String getShaderName() { 40 | return this.shaderName; 41 | } 42 | 43 | public PixelFormat getTargetFormat() { 44 | return this.targetFormat; 45 | } 46 | 47 | public Texture.WrapMode getTargetWrapMode() { 48 | return this.targetWrapMode; 49 | } 50 | 51 | public boolean isTargetMipmaps() { 52 | return this.targetMipmaps; 53 | } 54 | 55 | public ImagePoolPolicy getTargetPoolPolicy() { 56 | return this.targetPoolPolicy; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/TwoSamplerEffect.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect; 2 | 3 | import javafx.beans.property.ObjectProperty; 4 | import javafx.scene.effect.Effect; 5 | 6 | public abstract class TwoSamplerEffect extends ShaderEffect { 7 | 8 | protected TwoSamplerEffect() { 9 | super(2); 10 | } 11 | 12 | public void setPrimaryInput(Effect value) { 13 | this.primaryInputProperty().set(value); 14 | } 15 | 16 | public Effect getPrimaryInput() { 17 | return this.primaryInputProperty().get(); 18 | } 19 | 20 | public ObjectProperty primaryInputProperty() { 21 | return super.getInputProperty(0); 22 | } 23 | 24 | public void setSecondaryInput(Effect value) { 25 | this.secondaryInputProperty().set(value); 26 | } 27 | 28 | public Effect getSecondaryInput() { 29 | return this.secondaryInputProperty().get(); 30 | } 31 | 32 | public ObjectProperty secondaryInputProperty() { 33 | return super.getInputProperty(1); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/internal/DefaultEffectRenderer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect.internal; 2 | 3 | import com.sun.javafx.geom.Rectangle; 4 | import com.sun.javafx.geom.transform.BaseTransform; 5 | import com.sun.scenario.effect.FilterContext; 6 | import com.sun.scenario.effect.ImageData; 7 | import com.sun.scenario.effect.impl.state.RenderState; 8 | 9 | import de.teragam.jfxshader.effect.IEffectRenderer; 10 | import de.teragam.jfxshader.effect.InternalEffect; 11 | 12 | public class DefaultEffectRenderer implements IEffectRenderer { 13 | 14 | @Override 15 | public ImageData render(InternalEffect effect, FilterContext fctx, BaseTransform transform, Rectangle outputClip, RenderState rstate, ImageData... inputs) { 16 | return effect.getPeerMap().values().stream().findFirst() 17 | .map(peer -> peer.filter(effect, rstate, transform, outputClip, inputs)) 18 | .orElse(new ImageData(fctx, null, null)); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/internal/EffectRenderTimer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect.internal; 2 | 3 | import java.lang.ref.WeakReference; 4 | import java.util.HashMap; 5 | import java.util.Iterator; 6 | import java.util.Map; 7 | import java.util.UUID; 8 | 9 | import javafx.animation.AnimationTimer; 10 | 11 | public final class EffectRenderTimer { 12 | 13 | private static final EffectRenderTimer INSTANCE = new EffectRenderTimer(); 14 | 15 | private final Map> effects; 16 | 17 | private EffectRenderTimer() { 18 | this.effects = new HashMap<>(); 19 | final AnimationTimer timer = new AnimationTimer() { 20 | @Override 21 | public void handle(long now) { 22 | for (final Iterator> it = EffectRenderTimer.this.effects.values().iterator(); it.hasNext(); ) { 23 | final ShaderEffectBase e = it.next().get(); 24 | if (e != null) { 25 | if (e.isContinuousRendering()) { 26 | e.markDirty(); 27 | } else { 28 | it.remove(); 29 | } 30 | } else { 31 | it.remove(); 32 | } 33 | } 34 | } 35 | }; 36 | timer.start(); 37 | } 38 | 39 | public void register(ShaderEffectBase effect) { 40 | this.effects.put(effect.getEffectID(), new WeakReference<>(effect)); 41 | } 42 | 43 | public static EffectRenderTimer getInstance() { 44 | return INSTANCE; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/internal/PPSMultiSamplerPeer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect.internal; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.EnumMap; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.function.BiFunction; 9 | 10 | import com.sun.javafx.geom.Rectangle; 11 | import com.sun.javafx.geom.transform.BaseTransform; 12 | import com.sun.prism.PixelFormat; 13 | import com.sun.prism.RTTexture; 14 | import com.sun.prism.Texture; 15 | import com.sun.prism.impl.BaseContext; 16 | import com.sun.prism.impl.BaseGraphics; 17 | import com.sun.prism.impl.VertexBuffer; 18 | import com.sun.prism.impl.ps.BaseShaderContext; 19 | import com.sun.prism.impl.ps.BaseShaderGraphics; 20 | import com.sun.prism.ps.ShaderGraphics; 21 | import com.sun.scenario.effect.Effect; 22 | import com.sun.scenario.effect.ImageData; 23 | import com.sun.scenario.effect.impl.EffectPeer; 24 | import com.sun.scenario.effect.impl.PoolFilterable; 25 | import com.sun.scenario.effect.impl.prism.PrDrawable; 26 | import com.sun.scenario.effect.impl.prism.ps.PPSDrawable; 27 | import com.sun.scenario.effect.impl.prism.ps.PPSRenderer; 28 | import com.sun.scenario.effect.impl.state.RenderState; 29 | 30 | import de.teragam.jfxshader.ImagePoolPolicy; 31 | import de.teragam.jfxshader.JFXShader; 32 | import de.teragam.jfxshader.ShaderController; 33 | import de.teragam.jfxshader.ShaderDeclaration; 34 | import de.teragam.jfxshader.effect.InternalEffect; 35 | import de.teragam.jfxshader.effect.ShaderEffect; 36 | import de.teragam.jfxshader.effect.ShaderEffectPeerConfig; 37 | import de.teragam.jfxshader.exception.ShaderException; 38 | import de.teragam.jfxshader.exception.TextureCreationException; 39 | import de.teragam.jfxshader.util.Reflect; 40 | 41 | public abstract class PPSMultiSamplerPeer extends EffectPeer { 42 | 43 | private static final EnumMap FORMAT_IMAGE_POOL_MAP = new EnumMap<>(PixelFormat.class); 44 | 45 | private JFXShader shader; 46 | private PPSDrawable drawable; 47 | private BaseTransform transform; 48 | private Rectangle outputClip; 49 | private boolean invalidateShader; 50 | 51 | private final ArrayList textureCoords; 52 | private final ShaderEffectPeerConfig config; 53 | 54 | private final int checkTextureOpMask; 55 | 56 | protected PPSMultiSamplerPeer(ShaderEffectPeerConfig options) { 57 | super(options.getFilterContext(), options.getRenderer(), options.getShaderName()); 58 | this.textureCoords = new ArrayList<>(); 59 | this.config = Objects.requireNonNull(options, "ShaderEffectPeerConfig must not be null"); 60 | this.checkTextureOpMask = Reflect.on(BaseShaderContext.class).getFieldValue("CHECK_TEXTURE_OP_MASK", null); 61 | } 62 | 63 | @Override 64 | public void dispose() { 65 | if (this.shader != null) { 66 | this.shader.dispose(); 67 | } 68 | } 69 | 70 | private JFXShader createShader() { 71 | return ShaderController.createShader(super.getFilterContext(), this.createShaderDeclaration()); 72 | } 73 | 74 | protected abstract ShaderDeclaration createShaderDeclaration(); 75 | 76 | protected abstract void updateShader(JFXShader shader, S effect); 77 | 78 | public BaseTransform getTransform() { 79 | return this.transform; 80 | } 81 | 82 | protected void setTransform(BaseTransform transform) { 83 | this.transform = transform; 84 | } 85 | 86 | public PPSDrawable getDrawable() { 87 | return this.drawable; 88 | } 89 | 90 | protected void setOutputClip(Rectangle outputClip) { 91 | this.outputClip = outputClip; 92 | } 93 | 94 | public Rectangle getOutputClip() { 95 | return this.outputClip; 96 | } 97 | 98 | public float[] getTextureCoords(int inputIndex) { 99 | return Arrays.copyOf(this.textureCoords.get(inputIndex), this.textureCoords.get(inputIndex).length); 100 | } 101 | 102 | @Override 103 | public boolean isOriginUpperLeft() { 104 | return super.isOriginUpperLeft(); 105 | } 106 | 107 | @Override 108 | protected final PPSRenderer getRenderer() { 109 | return (PPSRenderer) super.getRenderer(); 110 | } 111 | 112 | @Override 113 | public final ImageData filter(final Effect effect, final T renderState, final BaseTransform transform, final Rectangle outputClip, 114 | final ImageData... inputs) { 115 | this.setEffect(effect); 116 | this.setRenderState(renderState); 117 | this.setTransform(transform); 118 | this.setOutputClip(outputClip); 119 | this.setDestBounds(this.getResultBounds(transform, outputClip, inputs)); 120 | return this.filterImpl(inputs); 121 | } 122 | 123 | protected ImageData filterImpl(ImageData... inputs) { 124 | final Rectangle dstBounds = this.getDestBounds(); 125 | final int dstw = dstBounds.width; 126 | final int dsth = dstBounds.height; 127 | final PPSRenderer renderer = this.getRenderer(); 128 | final PPSDrawable dst = this.getCompatibleImage(dstw, dsth, this.config.getTargetFormat(), this.config.getTargetWrapMode(), 129 | this.config.isTargetMipmaps(), this.config.getTargetPoolPolicy()); 130 | this.drawable = dst; 131 | if (dst == null) { 132 | this.markLost(renderer); 133 | return new ImageData(this.getFilterContext(), null, dstBounds); 134 | } 135 | this.setDestNativeBounds(dst.getPhysicalWidth(), dst.getPhysicalHeight()); 136 | 137 | final ArrayList coords = new ArrayList<>(); 138 | this.textureCoords.clear(); 139 | final ArrayList coordLength = new ArrayList<>(); 140 | final ArrayList textures = new ArrayList<>(); 141 | for (int i = 0; i < Math.min(inputs.length, 2); i++) { 142 | final PrDrawable srcTexture = (PrDrawable) inputs[i].getUntransformedImage(); 143 | if (srcTexture == null || srcTexture.getTextureObject() == null) { 144 | this.markLost(renderer); 145 | return new ImageData(this.getFilterContext(), dst, dstBounds); 146 | } 147 | final Rectangle srcBounds = inputs[i].getUntransformedBounds(); 148 | final Texture prTexture = srcTexture.getTextureObject(); 149 | final BaseTransform srcTransform = inputs[i].getTransform(); 150 | this.setInputBounds(i, srcBounds); 151 | this.setInputTransform(i, srcTransform); 152 | this.setInputNativeBounds(i, srcTexture.getNativeBounds()); 153 | 154 | final float[] srcRect = new float[8]; 155 | final int srcCoords = this.getTextureCoordinates(0, srcRect, srcBounds.x, srcBounds.y, srcTexture.getPhysicalWidth(), 156 | srcTexture.getPhysicalHeight(), dstBounds, srcTransform); 157 | 158 | final float txOff = ((float) prTexture.getContentX()) / prTexture.getPhysicalWidth(); 159 | final float tyOff = ((float) prTexture.getContentY()) / prTexture.getPhysicalHeight(); 160 | if (srcCoords < 8) { 161 | coords.add(new float[]{txOff + srcRect[0], tyOff + srcRect[1], txOff + srcRect[2], tyOff + srcRect[3], txOff + srcRect[2], tyOff + srcRect[1], 162 | txOff + srcRect[0], tyOff + srcRect[3]}); 163 | coordLength.add(4); 164 | this.textureCoords.add(new float[]{txOff + srcRect[0], tyOff + srcRect[1], txOff + srcRect[2], tyOff + srcRect[3]}); 165 | } else { 166 | coords.add(new float[]{txOff + srcRect[0], tyOff + srcRect[1], txOff + srcRect[2], tyOff + srcRect[3], txOff + srcRect[4], tyOff + srcRect[5], 167 | txOff + srcRect[6], tyOff + srcRect[7]}); 168 | coordLength.add(8); 169 | this.textureCoords.add(new float[]{txOff + srcRect[0], tyOff + srcRect[1], txOff + srcRect[4], tyOff + srcRect[5], 170 | txOff + srcRect[6], tyOff + srcRect[7], txOff + srcRect[2], tyOff + srcRect[3]}); 171 | } 172 | textures.add(srcTexture.getTextureObject()); 173 | } 174 | for (int i = 2; i < Math.min(inputs.length, ShaderController.MAX_BOUND_TEXTURES); i++) { 175 | final PrDrawable srcTexture = (PrDrawable) inputs[i].getUntransformedImage(); 176 | if (srcTexture == null || srcTexture.getTextureObject() == null) { 177 | this.markLost(renderer); 178 | return new ImageData(this.getFilterContext(), dst, dstBounds); 179 | } 180 | textures.add(srcTexture.getTextureObject()); 181 | } 182 | final ShaderGraphics g = dst.createGraphics(); 183 | if (g == null) { 184 | this.markLost(renderer); 185 | return new ImageData(this.getFilterContext(), dst, dstBounds); 186 | } 187 | 188 | if (this.invalidateShader && this.shader != null) { 189 | this.shader.dispose(); 190 | this.shader = null; 191 | this.invalidateShader = false; 192 | } 193 | if (this.shader == null) { 194 | this.shader = this.createShader(); 195 | } 196 | if (this.shader == null || !this.shader.isValid()) { 197 | this.markLost(renderer); 198 | return new ImageData(this.getFilterContext(), dst, dstBounds); 199 | } 200 | g.setExternalShader(this.shader); 201 | this.updateShader(this.shader, (S) ((InternalEffect) super.getEffect()).getEffect()); 202 | this.drawTextures((float) dstw, (float) dsth, textures, coords, coordLength, (BaseShaderGraphics) g); 203 | g.setExternalShader(null); 204 | 205 | return new ImageData(this.getFilterContext(), dst, dstBounds); 206 | } 207 | 208 | private void markLost(PPSRenderer renderer) { 209 | Reflect.on(PPSRenderer.class).method("markLost").invoke(renderer); 210 | } 211 | 212 | private void drawTextures(float dx2, float dy2, List textures, List coords, List coordLength, BaseShaderGraphics g) { 213 | final BaseTransform xform = g.getTransformNoClone(); 214 | if (textures.isEmpty()) { 215 | return; 216 | } 217 | final BaseContext context = Reflect.on(BaseGraphics.class).getFieldValue("context", g); 218 | if (context.isDisposed() || !(context instanceof BaseShaderContext)) { 219 | return; 220 | } 221 | ShaderController.ensureTextureCapacity(this.getFilterContext(), (BaseShaderContext) context); 222 | Reflect.on(BaseShaderContext.class).method("checkState").invoke(context, g, this.checkTextureOpMask, xform, this.shader.getObject()); 223 | for (int i = 0; i < Math.min(textures.size(), ShaderController.MAX_BOUND_TEXTURES); i++) { 224 | Reflect.on(BaseShaderContext.class).method("setTexture").invoke(context, i, textures.get(i)); 225 | } 226 | Reflect.on(BaseShaderContext.class).method("updatePerVertexColor").invoke(context, null, g.getExtraAlpha()); 227 | final VertexBuffer vb = context.getVertexBuffer(); 228 | switch (coords.size()) { 229 | case 0: 230 | return; 231 | case 1: 232 | final float[] c = coords.get(0); 233 | if (coordLength.get(0) < 8) { 234 | vb.addQuad(0, 0, dx2, dy2, c[0], c[1], c[2], c[3]); 235 | } else { 236 | vb.addMappedQuad(0, 0, dx2, dy2, c[0], c[1], c[4], c[5], c[6], c[7], c[2], c[3]); 237 | } 238 | break; 239 | default: 240 | final float[] c1 = coords.get(0); 241 | final float[] c2 = coords.get(1); 242 | if (coordLength.get(0) < 8 && coordLength.get(1) < 8) { 243 | vb.addQuad(0, 0, dx2, dy2, c1[0], c1[1], c1[2], c1[3], c2[0], c2[1], c2[2], c2[3]); 244 | } else { 245 | vb.addMappedQuad(0, 0, dx2, dy2, c1[0], c1[1], c1[4], c1[5], c1[6], c1[7], c1[2], c1[3], c2[0], c2[1], c2[4], c2[5], c2[6], c2[7], 246 | c2[2], c2[3]); 247 | } 248 | } 249 | } 250 | 251 | public PPSDrawable getCompatibleImage(int width, int height, PixelFormat format, Texture.WrapMode wrapMode, boolean mipmaps, 252 | ImagePoolPolicy poolPolicy) { 253 | if (format == PixelFormat.INT_ARGB_PRE && !mipmaps && poolPolicy == ImagePoolPolicy.LENIENT) { 254 | return this.getRenderer().getCompatibleImage(width, height); 255 | } else { 256 | final BiFunction imageFactory = (w, h) -> { 257 | if (!this.validateRenderer()) { 258 | return null; 259 | } 260 | try { 261 | return (PPSDrawable) Reflect.on(PPSDrawable.class).method("create", RTTexture.class) 262 | .invoke(null, ShaderController.createRTTexture(this.getFilterContext(), format, wrapMode, w, h, mipmaps)); 263 | } catch (TextureCreationException e) { 264 | return null; 265 | } 266 | }; 267 | PPSMultiSamplerPeer.FORMAT_IMAGE_POOL_MAP.computeIfAbsent(format, f -> new PolicyBasedImagePool()); 268 | final PolicyBasedImagePool pool = PPSMultiSamplerPeer.FORMAT_IMAGE_POOL_MAP.get(format); 269 | return (PPSDrawable) pool.checkOut(this.getRenderer(), width, height, mipmaps, imageFactory, poolPolicy); 270 | } 271 | } 272 | 273 | /** 274 | * Clones the given texture and returns the clone with the specified dimensions and pixel format. 275 | *

276 | * In JavaFX, the actual textures that are used for rendering are often larger than the content they contain due to the use of an internal texture pool. 277 | * To render shader effects, the texture coordinates for the input textures are fitted to the content size, but the texture size is not. 278 | * This may cause problems for shaders that modify the texture coordinates and rely on the texture coordinates being in the range [0, 1] for the whole 279 | * input texture. 280 | * By cloning the texture and setting the texture size to the content size, the texture coordinates will be in the range [0, 1]. 281 | *

282 | * Alternatively, the calculated texture coordinates can be loaded into the shader as a uniform variable. 283 | * 284 | * @see ImagePoolPolicy 285 | */ 286 | public PrDrawable cloneTexture(PrDrawable srcTexture, int width, int height, PixelFormat dstFormat) { 287 | final PrDrawable fittedTexture = this.getCompatibleImage(width, height, dstFormat, 288 | srcTexture.getTextureObject().getWrapMode(), 289 | srcTexture.getTextureObject().getUseMipmap(), ImagePoolPolicy.EXACT); 290 | if (fittedTexture == null || fittedTexture.getTextureObject() == null || !this.validateRenderer()) { 291 | return null; 292 | } 293 | fittedTexture.createGraphics().blit(srcTexture.getTextureObject(), null, 0, 0, width, height, 0, 0, width, height); 294 | return fittedTexture; 295 | } 296 | 297 | /** 298 | * Queues the invalidation and disposal of the shader. 299 | * It will be recreated with {@link PPSMultiSamplerPeer#createShaderDeclaration()} when needed. 300 | */ 301 | protected void invalidateShader() { 302 | if (this.shader != null) { 303 | this.invalidateShader = true; 304 | } 305 | } 306 | 307 | private boolean validateRenderer() { 308 | try { 309 | return (boolean) Reflect.on(PPSRenderer.class).method("validate").invoke(this.getRenderer()); 310 | } catch (ShaderException ignored) { 311 | return false; 312 | } 313 | } 314 | 315 | } 316 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/internal/PolicyBasedImagePool.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect.internal; 2 | 3 | import java.lang.ref.SoftReference; 4 | import java.util.Iterator; 5 | import java.util.List; 6 | import java.util.function.BiFunction; 7 | 8 | import com.sun.prism.Texture; 9 | import com.sun.scenario.effect.impl.ImagePool; 10 | import com.sun.scenario.effect.impl.PoolFilterable; 11 | import com.sun.scenario.effect.impl.Renderer; 12 | import com.sun.scenario.effect.impl.prism.PrTexture; 13 | 14 | import de.teragam.jfxshader.ImagePoolPolicy; 15 | import de.teragam.jfxshader.exception.ShaderException; 16 | import de.teragam.jfxshader.util.Reflect; 17 | 18 | public class PolicyBasedImagePool { 19 | 20 | private final ImagePool imagePool; 21 | private final List> unlocked; 22 | private final List> locked; 23 | 24 | public PolicyBasedImagePool() { 25 | this.imagePool = Reflect.on(ImagePool.class).constructor().create(); 26 | try { 27 | this.unlocked = Reflect.on(ImagePool.class).getFieldValue("unlocked", this.imagePool); 28 | this.locked = Reflect.on(ImagePool.class).getFieldValue("locked", this.imagePool); 29 | } catch (ShaderException e) { 30 | throw new ShaderException("Failed to initialize PixelFormatImagePool", e); 31 | } 32 | } 33 | 34 | public synchronized PoolFilterable checkOut(Renderer renderer, int w, int h) { 35 | return this.checkOut(renderer, w, h, false, renderer::createCompatibleImage, ImagePoolPolicy.LENIENT); 36 | } 37 | 38 | public synchronized PoolFilterable checkOut(Renderer renderer, int w, int h, boolean targetMipmaps, 39 | BiFunction drawableSupplier, ImagePoolPolicy policy) { 40 | if (w <= 0 || h <= 0) { 41 | w = h = 1; 42 | } 43 | final int quant = policy.getQuantization(); 44 | w = renderer.getCompatibleWidth(((w + quant - 1) / quant) * quant); 45 | h = renderer.getCompatibleHeight(((h + quant - 1) / quant) * quant); 46 | 47 | final int finalW = w; 48 | final int finalH = h; 49 | 50 | Reflect.on(ImagePool.class).processFieldValue("numAccessed", null, numCheckedOut -> (long) numCheckedOut + 1); 51 | Reflect.on(ImagePool.class).processFieldValue("pixelsAccessed", null, pixelsAccessed -> (long) pixelsAccessed + ((long) finalW) * finalH); 52 | 53 | SoftReference chosenEntry = null; 54 | PoolFilterable chosenImage = null; 55 | int minDiff = Integer.MAX_VALUE; 56 | final Iterator> entries = this.unlocked.iterator(); 57 | while (entries.hasNext()) { 58 | final SoftReference entry = entries.next(); 59 | final PoolFilterable filterable = entry.get(); 60 | if (filterable == null) { 61 | entries.remove(); 62 | } else { 63 | final int ew = filterable.getMaxContentWidth(); 64 | final int eh = filterable.getMaxContentHeight(); 65 | final int diff = (ew - w) * (eh - h); 66 | final boolean lenient = ew >= w && eh >= h && ew * eh / 2 <= w * h && (chosenEntry == null || diff < minDiff); 67 | final boolean exact = ew == w && eh == h; 68 | if ((policy.isApproximateMatch() && lenient) || (!policy.isApproximateMatch() && exact)) { 69 | final Texture tex = Reflect.on(PrTexture.class).getFieldValue("tex", filterable); 70 | if (tex.getUseMipmap() == targetMipmaps) { 71 | filterable.lock(); 72 | if (filterable.isLost()) { 73 | entries.remove(); 74 | continue; 75 | } 76 | if (chosenImage != null) { 77 | chosenImage.unlock(); 78 | } 79 | chosenEntry = entry; 80 | chosenImage = filterable; 81 | minDiff = diff; 82 | if (exact) { 83 | break; 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | if (chosenEntry != null) { 91 | this.unlocked.remove(chosenEntry); 92 | this.locked.add(chosenEntry); 93 | renderer.clearImage(chosenImage); 94 | return chosenImage; 95 | } 96 | 97 | this.locked.removeIf(entry -> entry.get() == null); 98 | 99 | PoolFilterable img = null; 100 | try { 101 | img = drawableSupplier.apply(w, h); 102 | } catch (OutOfMemoryError ignored) { 103 | Reflect.on(ImagePool.class).method("pruneCache").invoke(null); 104 | try { 105 | img = drawableSupplier.apply(w, h); 106 | } catch (OutOfMemoryError ignoredEx) { 107 | // ignored 108 | } 109 | } 110 | 111 | if (img != null) { 112 | img.setImagePool(this.imagePool); 113 | this.locked.add(new SoftReference<>(img)); 114 | Reflect.on(ImagePool.class).processFieldValue("numCreated", null, numCreated -> (long) numCreated + 1); 115 | Reflect.on(ImagePool.class).processFieldValue("pixelsCreated", null, pixelsCreated -> (long) pixelsCreated + ((long) finalW) * finalH); 116 | } 117 | return img; 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/internal/RTTTextureHelper.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect.internal; 2 | 3 | import com.sun.prism.PixelFormat; 4 | import com.sun.prism.Texture; 5 | import com.sun.prism.impl.BaseTexture; 6 | import com.sun.prism.impl.ManagedResource; 7 | 8 | import de.teragam.jfxshader.exception.ShaderException; 9 | import de.teragam.jfxshader.util.Reflect; 10 | 11 | public class RTTTextureHelper { 12 | 13 | protected RTTTextureHelper() {} 14 | 15 | protected static > void fillTexture(BaseTexture texture, T resource, PixelFormat format, Texture.WrapMode wrapMode, 16 | int physicalWidth, int physicalHeight, int contentX, int contentY, 17 | int contentWidth, int contentHeight, int maxContentWidth, int maxContentHeight, 18 | boolean useMipmap) { 19 | try { 20 | final Reflect reflect = Reflect.on(BaseTexture.class); 21 | reflect.setFieldValue("resource", texture, resource); 22 | reflect.setFieldValue("format", texture, format); 23 | reflect.setFieldValue("wrapMode", texture, wrapMode); 24 | reflect.setFieldValue("physicalWidth", texture, physicalWidth); 25 | reflect.setFieldValue("physicalHeight", texture, physicalHeight); 26 | reflect.setFieldValue("contentX", texture, contentX); 27 | reflect.setFieldValue("contentY", texture, contentY); 28 | reflect.setFieldValue("contentWidth", texture, contentWidth); 29 | reflect.setFieldValue("contentHeight", texture, contentHeight); 30 | reflect.setFieldValue("maxContentWidth", texture, maxContentWidth); 31 | reflect.setFieldValue("maxContentHeight", texture, maxContentHeight); 32 | reflect.setFieldValue("useMipmap", texture, useMipmap); 33 | texture.setLinearFiltering(true); 34 | } catch (ShaderException e) { 35 | throw new ShaderException("Could not fill texture", e); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/internal/ShaderEffectBase.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect.internal; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.UUID; 6 | 7 | import javafx.beans.property.ObjectProperty; 8 | import javafx.scene.Node; 9 | import javafx.scene.effect.Blend; 10 | import javafx.scene.effect.Effect; 11 | 12 | import com.sun.javafx.effect.EffectDirtyBits; 13 | import com.sun.javafx.geom.BaseBounds; 14 | import com.sun.javafx.geom.transform.BaseTransform; 15 | import com.sun.javafx.scene.BoundsAccessor; 16 | import com.sun.scenario.effect.impl.EffectPeer; 17 | import com.sun.scenario.effect.impl.state.RenderState; 18 | 19 | import de.teragam.jfxshader.effect.InternalEffect; 20 | import de.teragam.jfxshader.effect.ShaderEffect; 21 | import de.teragam.jfxshader.util.Reflect; 22 | 23 | public class ShaderEffectBase extends Blend { 24 | 25 | public static final int MAX_INPUTS = 2; 26 | private static final Reflect EFFECT_REFLECT = Reflect.on(Effect.class); 27 | 28 | private final ShaderEffect effect; 29 | private final InternalEffect peer; 30 | private final UUID effectID; 31 | 32 | private boolean continuousRendering; 33 | 34 | public ShaderEffectBase(ShaderEffect effect, int inputs) { 35 | if (inputs < 1 || inputs > MAX_INPUTS) { 36 | throw new IllegalArgumentException(String.format("Only 1 to %d inputs are supported. Requested were %d.", MAX_INPUTS, inputs)); 37 | } 38 | this.effectID = UUID.randomUUID(); 39 | this.effect = effect; 40 | this.peer = new InternalEffect(effect, inputs); 41 | EFFECT_REFLECT.setFieldValue("peer", this, this.peer); 42 | } 43 | 44 | public void setContinuousRendering(boolean continuousRendering) { 45 | if (continuousRendering && !this.continuousRendering) { 46 | EffectRenderTimer.getInstance().register(this); 47 | } 48 | this.continuousRendering = continuousRendering; 49 | } 50 | 51 | public boolean isContinuousRendering() { 52 | return this.continuousRendering; 53 | } 54 | 55 | public ShaderEffect getJFXShaderEffect() { 56 | return this.effect; 57 | } 58 | 59 | public void markDirty() { 60 | EFFECT_REFLECT.method("markDirty", EffectDirtyBits.class).invoke(this, EffectDirtyBits.EFFECT_DIRTY); 61 | } 62 | 63 | public void updateInputs() { 64 | final List> properties = List.of(this.topInputProperty(), this.bottomInputProperty()); 65 | for (int i = 0; i < properties.size(); i++) { 66 | final ObjectProperty property = properties.get(i); 67 | if (property != null) { 68 | final Effect localInput = property.get(); 69 | if (localInput != null) { 70 | EFFECT_REFLECT.method("sync").invoke(localInput); 71 | } 72 | this.peer.setInput(i, localInput == null ? null : (com.sun.scenario.effect.Effect) EFFECT_REFLECT.method("getPeer").invoke(localInput)); 73 | } 74 | } 75 | } 76 | 77 | public Map> getPeerMap() { 78 | return this.peer.getPeerMap(); 79 | } 80 | 81 | public UUID getEffectID() { 82 | return this.effectID; 83 | } 84 | 85 | public static BaseBounds getInputBounds(BaseBounds bounds, BaseTransform tx, Node node, BoundsAccessor boundsAccessor, Effect input) { 86 | return EFFECT_REFLECT.method("getInputBounds").invoke(null, bounds, tx, node, boundsAccessor, input); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/internal/d3d/D3DRTTextureHelper.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect.internal.d3d; 2 | 3 | import com.sun.prism.PixelFormat; 4 | import com.sun.prism.RTTexture; 5 | import com.sun.prism.ResourceFactory; 6 | import com.sun.prism.Texture; 7 | import com.sun.prism.d3d.D3DTextureData; 8 | import com.sun.prism.impl.BaseResourceFactory; 9 | import com.sun.prism.impl.BaseResourcePool; 10 | import com.sun.prism.impl.BaseTexture; 11 | import com.sun.prism.impl.DisposerManagedResource; 12 | import com.sun.prism.impl.PrismSettings; 13 | import com.sun.prism.impl.ps.BaseShaderContext; 14 | 15 | import de.teragam.jfxshader.effect.internal.RTTTextureHelper; 16 | import de.teragam.jfxshader.exception.TextureCreationException; 17 | import de.teragam.jfxshader.util.Reflect; 18 | 19 | public final class D3DRTTextureHelper extends RTTTextureHelper { 20 | 21 | private D3DRTTextureHelper() {} 22 | 23 | public static RTTexture createD3DRTTexture(BaseResourceFactory factory, PixelFormat format, Texture.WrapMode wrapMode, int width, int height, 24 | boolean useMipmap) { 25 | final Class d3DResourceFactoryClass = Reflect.resolveClass("com.sun.prism.d3d.D3DResourceFactory"); 26 | if (!d3DResourceFactoryClass.isInstance(factory)) { 27 | throw new TextureCreationException("Factory is not a D3DResourceFactory"); 28 | } 29 | if (factory.isDisposed()) { 30 | throw new TextureCreationException("Failed to create texture: The factory is disposed."); 31 | } 32 | final BaseResourcePool d3dVramPool = Reflect.on("com.sun.prism.d3d.D3DVramPool").getFieldValue("instance", null); 33 | if (!d3dVramPool.prepareForAllocation(D3DRTTextureHelper.estimateTextureSize(width, height, format))) { 34 | throw new TextureCreationException("Failed to create texture: Not enough VRAM."); 35 | } 36 | 37 | int createWidth = width; 38 | int createHeight = height; 39 | if (PrismSettings.forcePow2) { 40 | createWidth = D3DRTTextureHelper.nextPowerOfTwo(createWidth); 41 | createHeight = D3DRTTextureHelper.nextPowerOfTwo(createHeight); 42 | } 43 | final BaseShaderContext context = Reflect.on(d3DResourceFactoryClass).getFieldValue("context", factory); 44 | final long contextHandle = Reflect.on("com.sun.prism.d3d.D3DContext").getFieldValue("pContext", context); 45 | final long pResource = (long) Reflect.on(d3DResourceFactoryClass).method("nCreateTexture") 46 | .invoke(null, contextHandle, format.ordinal(), Texture.Usage.DEFAULT.ordinal(), true, createWidth, createHeight, 0, useMipmap); 47 | 48 | if (pResource == 0L) { 49 | throw new TextureCreationException("Failed to create texture."); 50 | } 51 | 52 | final int textureWidth = (int) Reflect.on(d3DResourceFactoryClass).method("nGetTextureWidth").invoke(null, pResource); 53 | final int textureHeight = (int) Reflect.on(d3DResourceFactoryClass).method("nGetTextureHeight").invoke(null, pResource); 54 | final RTTexture rtt = Reflect.on("com.sun.prism.d3d.D3DRTTexture").allocateInstance(); 55 | final DisposerManagedResource resource = Reflect.>on("com.sun.prism.d3d.D3DTextureResource") 56 | .constructor().create(Reflect.on(D3DTextureData.class).constructor() 57 | .create(context, pResource, true, textureWidth, textureHeight, format, 0)); 58 | RTTTextureHelper.fillTexture((BaseTexture>) rtt, resource, PixelFormat.INT_ARGB_PRE, wrapMode, 59 | textureWidth, textureHeight, 0, 0, width, height, 60 | textureWidth, textureHeight, useMipmap); 61 | rtt.setOpaque(false); 62 | rtt.createGraphics().clear(); 63 | return rtt; 64 | } 65 | 66 | public static long getDevice(ResourceFactory factory) { 67 | final Class d3DResourceFactoryClass = Reflect.resolveClass("com.sun.prism.d3d.D3DResourceFactory"); 68 | if (!d3DResourceFactoryClass.isInstance(factory)) { 69 | throw new TextureCreationException("Factory is not a D3DResourceFactory"); 70 | } 71 | final BaseShaderContext context = Reflect.on(d3DResourceFactoryClass).getFieldValue("context", factory); 72 | final long contextHandle = Reflect.on("com.sun.prism.d3d.D3DContext").getFieldValue("pContext", context); 73 | return (long) Reflect.on(d3DResourceFactoryClass).method("nGetDevice").invoke(null, contextHandle); 74 | } 75 | 76 | public static long estimateTextureSize(long width, long height, PixelFormat format) { 77 | return width * height * format.getBytesPerPixelUnit(); 78 | } 79 | 80 | private static int nextPowerOfTwo(int val) { 81 | int i = 1; 82 | while (i < val) { 83 | i *= 2; 84 | } 85 | return i; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/internal/es2/ES2RTTextureHelper.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect.internal.es2; 2 | 3 | import java.nio.Buffer; 4 | 5 | import com.sun.prism.PixelFormat; 6 | import com.sun.prism.RTTexture; 7 | import com.sun.prism.Texture; 8 | import com.sun.prism.impl.BaseResourceFactory; 9 | import com.sun.prism.impl.BaseTexture; 10 | import com.sun.prism.impl.DisposerManagedResource; 11 | import com.sun.prism.impl.PrismSettings; 12 | import com.sun.prism.impl.PrismTrace; 13 | import com.sun.prism.impl.TextureResourcePool; 14 | import com.sun.prism.impl.ps.BaseShaderContext; 15 | 16 | import de.teragam.jfxshader.effect.internal.RTTTextureHelper; 17 | import de.teragam.jfxshader.exception.TextureCreationException; 18 | import de.teragam.jfxshader.util.MethodInvocationWrapper; 19 | import de.teragam.jfxshader.util.Reflect; 20 | 21 | public class ES2RTTextureHelper extends RTTTextureHelper { 22 | 23 | private static final int GL_TEXTURE_2D = 50; 24 | private static final int GL_LINEAR = 53; 25 | 26 | private ES2RTTextureHelper() {} 27 | 28 | public static Object getGLContext(BaseResourceFactory factory) { 29 | if (!Reflect.resolveClass("com.sun.prism.es2.ES2ResourceFactory").isInstance(factory)) { 30 | throw new TextureCreationException("Factory is not a ES2ResourceFactory"); 31 | } 32 | final BaseShaderContext context = ReflectionES2Helper.getInstance().getContext(factory); 33 | return Reflect.on(context.getClass()).getFieldValue("glContext", context); 34 | } 35 | 36 | public static RTTexture createES2RTTexture(BaseResourceFactory factory, PixelFormat format, Texture.WrapMode wrapMode, int width, int height, 37 | boolean useMipmap) { 38 | if (!Reflect.resolveClass("com.sun.prism.es2.ES2ResourceFactory").isInstance(factory)) { 39 | throw new TextureCreationException("Factory is not a ES2ResourceFactory"); 40 | } 41 | final BaseShaderContext es2Context = ReflectionES2Helper.getInstance().getContext(factory); 42 | final GLContext glContext = Reflect.createProxy(ES2RTTextureHelper.getGLContext(factory), 43 | Reflect.resolveClass("com.sun.prism.es2.GLContext"), GLContext.class); 44 | 45 | final boolean pad; 46 | switch (wrapMode) { 47 | case CLAMP_NOT_NEEDED: 48 | pad = false; 49 | break; 50 | case CLAMP_TO_ZERO: 51 | pad = !glContext.canClampToZero(); 52 | break; 53 | default: 54 | case CLAMP_TO_EDGE: 55 | case REPEAT: 56 | throw new IllegalArgumentException("wrap mode not supported for RT textures: " + wrapMode); 57 | case CLAMP_TO_EDGE_SIMULATED: 58 | case CLAMP_TO_ZERO_SIMULATED: 59 | case REPEAT_SIMULATED: 60 | throw new IllegalArgumentException("Cannot request simulated wrap mode: " + wrapMode); 61 | } 62 | 63 | final int contentX; 64 | final int contentY; 65 | final int paddedW; 66 | final int paddedH; 67 | if (pad) { 68 | contentX = 1; 69 | contentY = 1; 70 | paddedW = width + 2; 71 | paddedH = height + 2; 72 | wrapMode = wrapMode.simulatedVersion(); 73 | } else { 74 | contentX = 0; 75 | contentY = 0; 76 | paddedW = width; 77 | paddedH = height; 78 | } 79 | 80 | final int maxSize = glContext.getMaxTextureSize(); 81 | int texWidth; 82 | int texHeight; 83 | if (glContext.canCreateNonPowTwoTextures()) { 84 | texWidth = (paddedW <= maxSize) ? paddedW : 0; 85 | texHeight = (paddedH <= maxSize) ? paddedH : 0; 86 | } else { 87 | texWidth = ES2RTTextureHelper.nextPowerOfTwo(paddedW, maxSize); 88 | texHeight = ES2RTTextureHelper.nextPowerOfTwo(paddedH, maxSize); 89 | } 90 | 91 | if (texWidth == 0 || texHeight == 0) { 92 | throw new TextureCreationException( 93 | "Requested texture dimensions (" + width + "x" + height + ") " 94 | + "require dimensions (" + texWidth + "x" + texHeight + ") " 95 | + "that exceed maximum texture size (" + maxSize + ")"); 96 | } 97 | 98 | texWidth = Math.max(texWidth, PrismSettings.minRTTSize); 99 | texHeight = Math.max(texHeight, PrismSettings.minRTTSize); 100 | final TextureResourcePool es2VramPool = Reflect.on("com.sun.prism.es2.ES2VramPool").getFieldValue("instance", null); 101 | final long size = es2VramPool.estimateTextureSize(texWidth, texHeight, format); 102 | if (!es2VramPool.prepareForAllocation(size)) { 103 | throw new TextureCreationException("Failed to create texture: Not enough VRAM."); 104 | } 105 | 106 | glContext.setActiveTextureUnit(0); 107 | final int savedFBO = glContext.getBoundFBO(); 108 | final int savedTex = glContext.getBoundTexture(); 109 | 110 | final int nativeTexID = glContext.genAndBindTexture(); 111 | if (nativeTexID == 0L) { 112 | throw new TextureCreationException("Failed to create texture."); 113 | } 114 | 115 | final boolean result = ReflectionES2Helper.getInstance() 116 | .uploadPixels(glContext.getObject(), GL_TEXTURE_2D, null, format, texWidth, texHeight, contentX, 117 | contentY, 0, 0, width, height, 0, true, useMipmap); 118 | if (!result) { 119 | throw new TextureCreationException("Failed to create texture."); 120 | } 121 | 122 | glContext.texParamsMinMax(GL_LINEAR, useMipmap); 123 | 124 | final int nativeFBOID = glContext.createFBO(nativeTexID); 125 | if (nativeFBOID == 0) { 126 | glContext.deleteTexture(nativeTexID); 127 | throw new TextureCreationException("Failed to attach FBO to texture."); 128 | } 129 | 130 | final int padding = pad ? 2 : 0; 131 | final int maxContentW = texWidth - padding; 132 | final int maxContentH = texHeight - padding; 133 | final Object texData = Reflect.on("com.sun.prism.es2.ES2RTTextureData").constructor() 134 | .create(es2Context, nativeTexID, nativeFBOID, texWidth, texHeight, size); 135 | final DisposerManagedResource texRes = (DisposerManagedResource) Reflect.on("com.sun.prism.es2.ES2TextureResource") 136 | .constructor(Reflect.resolveClass("com.sun.prism.es2.ES2TextureData")).create(texData); 137 | final RTTexture es2RTT = ReflectionES2Helper.getInstance().createTexture(es2Context, texRes, format, wrapMode, texWidth, texHeight, contentX, contentY, 138 | width, height, maxContentW, maxContentH, useMipmap); 139 | 140 | glContext.bindFBO(savedFBO); 141 | glContext.setBoundTexture(savedTex); 142 | return es2RTT; 143 | } 144 | 145 | private static int nextPowerOfTwo(int val, int max) { 146 | if (val > max) { 147 | return 0; 148 | } 149 | int i = 1; 150 | while (i < val) { 151 | i *= 2; 152 | } 153 | return i; 154 | } 155 | 156 | private static class ReflectionES2Helper { 157 | 158 | private static ReflectionES2Helper instance; 159 | 160 | private final MethodInvocationWrapper uploadPixelsMethod; 161 | 162 | public ReflectionES2Helper() { 163 | this.uploadPixelsMethod = Reflect.on("com.sun.prism.es2.ES2Texture").method("uploadPixels", 164 | Reflect.resolveClass("com.sun.prism.es2.GLContext"), int.class, Buffer.class, PixelFormat.class, int.class, int.class, int.class, int.class, 165 | int.class, int.class, int.class, int.class, int.class, boolean.class, boolean.class); 166 | } 167 | 168 | public static ReflectionES2Helper getInstance() { 169 | if (ReflectionES2Helper.instance == null) { 170 | ReflectionES2Helper.instance = new ReflectionES2Helper(); 171 | } 172 | return ReflectionES2Helper.instance; 173 | } 174 | 175 | public BaseShaderContext getContext(BaseResourceFactory factory) { 176 | return Reflect.on("com.sun.prism.es2.ES2ResourceFactory").getFieldValue("context", factory); 177 | } 178 | 179 | public boolean uploadPixels(Object glCtx, int target, Buffer pixels, PixelFormat format, int texw, int texh, int dstx, int dsty, int srcx, int srcy, 180 | int srcw, int srch, int srcscan, boolean create, boolean useMipmap) { 181 | return this.uploadPixelsMethod.invoke(null, glCtx, target, pixels, format, texw, texh, dstx, dsty, srcx, srcy, srcw, srch, srcscan, 182 | create, useMipmap); 183 | } 184 | 185 | private RTTexture createTexture(BaseShaderContext es2Context, DisposerManagedResource resource, PixelFormat format, Texture.WrapMode wrapMode, 186 | int physicalWidth, int physicalHeight, int contentX, int contentY, int contentWidth, int contentHeight, 187 | int maxContentWidth, int maxContentHeight, boolean useMipmap) { 188 | final RTTexture texture = Reflect.on("com.sun.prism.es2.ES2RTTexture").allocateInstance(); 189 | texture.setOpaque(false); 190 | Reflect.on("com.sun.prism.es2.ES2Texture").setFieldValue("context", texture, es2Context); 191 | RTTTextureHelper.fillTexture((BaseTexture>) texture, resource, PixelFormat.INT_ARGB_PRE, wrapMode, 192 | physicalWidth, physicalHeight, contentX, contentY, contentWidth, contentHeight, maxContentWidth, maxContentHeight, useMipmap); 193 | PrismTrace.rttCreated(Reflect.on("com.sun.prism.es2.ES2RTTextureData").getFieldValue("fboID", resource.getResource()), 194 | physicalWidth, physicalHeight, format.getBytesPerPixelUnit()); 195 | return texture; 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/effect/internal/es2/GLContext.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.effect.internal.es2; 2 | 3 | import de.teragam.jfxshader.util.ReflectProxy; 4 | 5 | public interface GLContext extends ReflectProxy { 6 | 7 | boolean canClampToZero(); 8 | 9 | int getMaxTextureSize(); 10 | 11 | boolean canCreateNonPowTwoTextures(); 12 | 13 | void setActiveTextureUnit(int unit); 14 | 15 | int getBoundFBO(); 16 | 17 | int getBoundTexture(); 18 | 19 | int genAndBindTexture(); 20 | 21 | void texParamsMinMax(int pname, boolean useMipmap); 22 | 23 | int createFBO(int texID); 24 | 25 | void deleteTexture(int tID); 26 | 27 | void bindFBO(int nativeFBOID); 28 | 29 | void setBoundTexture(int texid); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/exception/ShaderCreationException.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.exception; 2 | 3 | public class ShaderCreationException extends ShaderException { 4 | 5 | public ShaderCreationException(String message) { 6 | super(message); 7 | } 8 | 9 | public ShaderCreationException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/exception/ShaderException.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.exception; 2 | 3 | public class ShaderException extends RuntimeException { 4 | 5 | public ShaderException(String message) { 6 | super(message); 7 | } 8 | 9 | public ShaderException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/exception/TextureCreationException.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.exception; 2 | 3 | public class TextureCreationException extends ShaderException { 4 | 5 | public TextureCreationException(String message) { 6 | super(message); 7 | } 8 | 9 | public TextureCreationException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/MaterialDependency.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Documented 10 | @Target(ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface MaterialDependency { 13 | 14 | Class> value(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/ShaderMaterial.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material; 2 | 3 | import javafx.beans.Observable; 4 | import javafx.beans.property.BooleanProperty; 5 | import javafx.beans.property.DoubleProperty; 6 | import javafx.beans.property.IntegerProperty; 7 | import javafx.beans.property.ObjectProperty; 8 | import javafx.beans.property.SimpleBooleanProperty; 9 | import javafx.beans.property.SimpleDoubleProperty; 10 | import javafx.beans.property.SimpleIntegerProperty; 11 | import javafx.beans.property.SimpleObjectProperty; 12 | import javafx.scene.image.Image; 13 | import javafx.scene.paint.Material; 14 | 15 | import com.sun.javafx.beans.event.AbstractNotifyListener; 16 | import com.sun.javafx.tk.Toolkit; 17 | 18 | import de.teragam.jfxshader.MaterialController; 19 | import de.teragam.jfxshader.material.internal.ShaderMaterialBase; 20 | 21 | public abstract class ShaderMaterial { 22 | 23 | private final ShaderMaterialBase materialBase; 24 | 25 | protected ShaderMaterial() { 26 | MaterialController.setup3D(); 27 | this.materialBase = new ShaderMaterialBase(this); 28 | } 29 | 30 | public Material getFXMaterial() { 31 | return this.materialBase; 32 | } 33 | 34 | private void markDirty() { 35 | this.materialBase.setDirty(true); 36 | } 37 | 38 | protected DoubleProperty createMaterialDoubleProperty(double value, String name) { 39 | return new SimpleDoubleProperty(ShaderMaterial.this, name, value) { 40 | @Override 41 | public void invalidated() { 42 | ShaderMaterial.this.markDirty(); 43 | } 44 | }; 45 | } 46 | 47 | protected IntegerProperty createMaterialIntegerProperty(int value, String name) { 48 | return new SimpleIntegerProperty(ShaderMaterial.this, name, value) { 49 | @Override 50 | public void invalidated() { 51 | ShaderMaterial.this.markDirty(); 52 | } 53 | }; 54 | } 55 | 56 | protected BooleanProperty createMaterialBooleanProperty(boolean value, String name) { 57 | return new SimpleBooleanProperty(ShaderMaterial.this, name, value) { 58 | @Override 59 | public void invalidated() { 60 | ShaderMaterial.this.markDirty(); 61 | } 62 | }; 63 | } 64 | 65 | protected ObjectProperty createMaterialObjectProperty(T value, String name) { 66 | return new SimpleObjectProperty<>(ShaderMaterial.this, name, value) { 67 | @Override 68 | public void invalidated() { 69 | ShaderMaterial.this.markDirty(); 70 | } 71 | }; 72 | } 73 | 74 | protected ObjectProperty createMaterialImageProperty(Image value, String name) { 75 | return new SimpleObjectProperty<>(ShaderMaterial.this, name, value) { 76 | 77 | private boolean needsListeners; 78 | private Image lastImage; 79 | 80 | @Override 81 | public void invalidated() { 82 | final Image image = this.get(); 83 | 84 | if (this.needsListeners) { 85 | Toolkit.getImageAccessor().getImageProperty(this.lastImage) 86 | .removeListener(ShaderMaterial.this.platformImageChangeListener.getWeakListener()); 87 | } 88 | 89 | this.needsListeners = image != null && (Toolkit.getImageAccessor().isAnimation(image) || image.getProgress() < 1); 90 | if (this.needsListeners) { 91 | Toolkit.getImageAccessor().getImageProperty(image). 92 | addListener(ShaderMaterial.this.platformImageChangeListener.getWeakListener()); 93 | } 94 | 95 | this.lastImage = image; 96 | 97 | ShaderMaterial.this.markDirty(); 98 | } 99 | }; 100 | } 101 | 102 | private final AbstractNotifyListener platformImageChangeListener = new AbstractNotifyListener() { 103 | @Override 104 | public void invalidated(Observable valueModel) { 105 | ShaderMaterial.this.markDirty(); 106 | } 107 | }; 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/ShaderMaterialPeer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import javafx.scene.image.Image; 7 | 8 | import com.sun.javafx.geom.transform.BaseTransform; 9 | import com.sun.javafx.sg.prism.NGCamera; 10 | import com.sun.javafx.sg.prism.NGLightBase; 11 | import com.sun.prism.Graphics; 12 | import com.sun.prism.MeshView; 13 | import com.sun.prism.impl.ps.BaseShaderContext; 14 | import com.sun.scenario.effect.impl.prism.PrFilterContext; 15 | 16 | import de.teragam.jfxshader.JFXShader; 17 | import de.teragam.jfxshader.ShaderController; 18 | import de.teragam.jfxshader.ShaderDeclaration; 19 | import de.teragam.jfxshader.material.internal.AbstractShaderMaterialPeerRenderer; 20 | import de.teragam.jfxshader.material.internal.ShaderMeshView; 21 | import de.teragam.jfxshader.material.internal.d3d.D3DShaderMaterialPeerRenderer; 22 | import de.teragam.jfxshader.material.internal.es2.ES2ShaderMaterialPeerRenderer; 23 | import de.teragam.jfxshader.material.internal.es2.ES2ShaderMeshView; 24 | 25 | public abstract class ShaderMaterialPeer { 26 | 27 | private JFXShader vertexShader; 28 | private JFXShader pixelShader; 29 | private JFXShader es2Shader; 30 | 31 | private BaseTransform transform; 32 | private NGLightBase[] lights; 33 | private NGCamera camera; 34 | 35 | private final Map imageIndexMap; 36 | private final AbstractShaderMaterialPeerRenderer peerRenderer; 37 | 38 | protected ShaderMaterialPeer() { 39 | this.imageIndexMap = new HashMap<>(); 40 | if (ShaderController.isGLSLSupported()) { 41 | this.peerRenderer = new ES2ShaderMaterialPeerRenderer(); 42 | } else { 43 | this.peerRenderer = new D3DShaderMaterialPeerRenderer(); 44 | } 45 | } 46 | 47 | public Map getES2ShaderAttributes() { 48 | final Map attributes = new HashMap<>(); 49 | attributes.put("pos", 0); 50 | attributes.put("texCoords", 1); 51 | attributes.put("tangent", 2); 52 | return attributes; 53 | } 54 | 55 | protected void setSamplerImage(Image image, int index) { 56 | this.imageIndexMap.put(index, image); 57 | } 58 | 59 | public final void filter(Graphics g, MeshView meshView, BaseShaderContext context) { 60 | this.transform = g.getTransformNoClone(); 61 | this.lights = g.getLights(); 62 | this.camera = g.getCameraNoClone(); 63 | 64 | if (ShaderController.isHLSLSupported()) { 65 | this.filterD3D(g, (ShaderMeshView) meshView, context); 66 | } else if (ShaderController.isGLSLSupported()) { 67 | this.filterES2(g, (ES2ShaderMeshView) meshView, context); 68 | } 69 | } 70 | 71 | protected void filterD3D(Graphics g, ShaderMeshView meshView, BaseShaderContext context) { 72 | if (this.vertexShader == null) { 73 | this.vertexShader = this.createVertexShader(g); 74 | } 75 | if (this.pixelShader == null) { 76 | this.pixelShader = this.createPixelShader(g); 77 | } 78 | this.vertexShader.enable(); 79 | this.pixelShader.enable(); 80 | this.imageIndexMap.clear(); 81 | this.peerRenderer.updateMatrices(g, context); 82 | this.updateShader(this.vertexShader, this.pixelShader, (T) meshView.getMaterial().getShaderMaterial()); 83 | this.peerRenderer.render(g, meshView, context, this.imageIndexMap); 84 | } 85 | 86 | protected void filterES2(Graphics g, ES2ShaderMeshView meshView, BaseShaderContext context) { 87 | if (this.es2Shader == null) { 88 | this.es2Shader = this.createES2ShaderProgram(g); 89 | } 90 | this.es2Shader.enable(); 91 | this.imageIndexMap.clear(); 92 | this.peerRenderer.updateMatrices(g, context); 93 | this.updateShader(this.es2Shader, this.es2Shader, (T) meshView.getMaterial().getShaderMaterial()); 94 | this.peerRenderer.render(g, meshView, context, this.imageIndexMap); 95 | } 96 | 97 | private JFXShader createPixelShader(Graphics g) { 98 | return ShaderController.createShader(PrFilterContext.getInstance(g.getAssociatedScreen()), this.createPixelShaderDeclaration()); 99 | } 100 | 101 | private JFXShader createVertexShader(Graphics g) { 102 | return ShaderController.createVertexShader(PrFilterContext.getInstance(g.getAssociatedScreen()), this.createVertexShaderDeclaration()); 103 | } 104 | 105 | private JFXShader createES2ShaderProgram(Graphics g) { 106 | return ShaderController.createES2ShaderProgram(PrFilterContext.getInstance(g.getAssociatedScreen()), 107 | this.createVertexShaderDeclaration(), this.createPixelShaderDeclaration(), this.getES2ShaderAttributes()); 108 | } 109 | 110 | protected BaseTransform getTransform() { 111 | return this.transform; 112 | } 113 | 114 | protected NGLightBase[] getLights() { 115 | return this.lights; 116 | } 117 | 118 | protected NGCamera getCamera() { 119 | return this.camera; 120 | } 121 | 122 | protected float[] getWorldMatrix() { 123 | return this.peerRenderer.getWorldMatrix(); 124 | } 125 | 126 | protected float[] getViewProjectionMatrix() { 127 | return this.peerRenderer.getViewProjectionMatrix(); 128 | } 129 | 130 | public abstract ShaderDeclaration createPixelShaderDeclaration(); 131 | 132 | public abstract ShaderDeclaration createVertexShaderDeclaration(); 133 | 134 | protected abstract void updateShader(JFXShader vertexShader, JFXShader pixelShader, T material); 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/AbstractShaderMaterialPeerRenderer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal; 2 | 3 | import java.util.Map; 4 | 5 | import javafx.scene.image.Image; 6 | 7 | import com.sun.javafx.geom.transform.Affine3D; 8 | import com.sun.javafx.geom.transform.BaseTransform; 9 | import com.sun.javafx.geom.transform.GeneralTransform3D; 10 | import com.sun.javafx.tk.Toolkit; 11 | import com.sun.prism.Graphics; 12 | import com.sun.prism.Texture; 13 | import com.sun.prism.impl.ps.BaseShaderContext; 14 | 15 | import de.teragam.jfxshader.util.Reflect; 16 | 17 | public abstract class AbstractShaderMaterialPeerRenderer { 18 | 19 | private float[] worldMatrix = new float[16]; 20 | private float[] viewProjectionMatrix = new float[16]; 21 | 22 | private final GeneralTransform3D worldTx = new GeneralTransform3D(); 23 | private final GeneralTransform3D scratchTx = new GeneralTransform3D(); 24 | private final Affine3D scratchAffine3DTx = new Affine3D(); 25 | 26 | public void updateMatrices(Graphics g, BaseShaderContext context) { 27 | final Reflect contextReflect = Reflect.on(context.getClass()); 28 | final GeneralTransform3D projViewTx = contextReflect.getFieldValue("projViewTx", context); 29 | this.scratchTx.set(projViewTx); 30 | final float pixelScaleFactorX = g.getPixelScaleFactorX(); 31 | final float pixelScaleFactorY = g.getPixelScaleFactorY(); 32 | if (pixelScaleFactorX != 1.0 || pixelScaleFactorY != 1.0) { 33 | this.scratchTx.scale(pixelScaleFactorX, pixelScaleFactorY, 1.0); 34 | } 35 | this.viewProjectionMatrix = this.convertMatrix(this.scratchTx); 36 | 37 | final BaseTransform xform = g.getTransformNoClone(); 38 | if (pixelScaleFactorX != 1.0 || pixelScaleFactorY != 1.0) { 39 | this.scratchAffine3DTx.setToIdentity(); 40 | this.scratchAffine3DTx.scale(1.0 / pixelScaleFactorX, 1.0 / pixelScaleFactorY); 41 | this.scratchAffine3DTx.concatenate(xform); 42 | this.updateWorldTransform(this.scratchAffine3DTx); 43 | } else { 44 | this.updateWorldTransform(xform); 45 | } 46 | this.worldMatrix = this.convertMatrix(this.worldTx); 47 | } 48 | 49 | public abstract void render(Graphics g, ShaderMeshView meshView, BaseShaderContext context, Map imageIndexMap); 50 | 51 | protected abstract float[] convertMatrix(GeneralTransform3D src); 52 | 53 | protected Texture getTexture(BaseShaderContext context, Image image, boolean useMipmap) { 54 | if (image == null) { 55 | return null; 56 | } 57 | final com.sun.prism.Image platformImage = (com.sun.prism.Image) Toolkit.getImageAccessor().getPlatformImage(image); 58 | return platformImage == null ? null : context.getResourceFactory().getCachedTexture(platformImage, Texture.WrapMode.REPEAT, useMipmap); 59 | } 60 | 61 | private void updateWorldTransform(BaseTransform xform) { 62 | this.worldTx.setIdentity(); 63 | if ((xform != null) && (!xform.isIdentity())) { 64 | this.worldTx.mul(xform); 65 | } 66 | } 67 | 68 | public float[] getWorldMatrix() { 69 | return this.worldMatrix; 70 | } 71 | 72 | public float[] getViewProjectionMatrix() { 73 | return this.viewProjectionMatrix; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/BaseMeshHelper.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal; 2 | 3 | import com.sun.prism.impl.Disposer; 4 | 5 | public interface BaseMeshHelper extends Disposer.Record { 6 | boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, int[] indexBufferInt, int indexBufferLength); 7 | 8 | boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, short[] indexBufferShort, int indexBufferLength); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/InternalBasePhongMaterial.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal; 2 | 3 | import com.sun.prism.TextureMap; 4 | import com.sun.prism.impl.BasePhongMaterial; 5 | import com.sun.prism.impl.Disposer; 6 | 7 | import de.teragam.jfxshader.material.ShaderMaterial; 8 | 9 | public class InternalBasePhongMaterial extends BasePhongMaterial { 10 | 11 | private final ShaderMaterial material; 12 | 13 | public InternalBasePhongMaterial(ShaderMaterial material, Disposer.Record disposerRecord) { 14 | super(disposerRecord); 15 | this.material = material; 16 | } 17 | 18 | public static InternalBasePhongMaterial create(ShaderMaterial material) { 19 | return new InternalBasePhongMaterial(material, () -> {}); 20 | } 21 | 22 | public ShaderMaterial getShaderMaterial() { 23 | return this.material; 24 | } 25 | 26 | @Override 27 | public void setDiffuseColor(float r, float g, float b, float a) { 28 | // Not needed 29 | } 30 | 31 | @Override 32 | public void setSpecularColor(boolean set, float r, float g, float b, float a) { 33 | // Not needed 34 | } 35 | 36 | @Override 37 | public void setTextureMap(TextureMap map) { 38 | // Not needed 39 | } 40 | 41 | @Override 42 | public void lockTextureMaps() { 43 | // Not needed 44 | } 45 | 46 | @Override 47 | public void unlockTextureMaps() { 48 | // Not needed 49 | } 50 | 51 | @Override 52 | public void dispose() { 53 | this.disposerRecord.dispose(); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/InternalNGBox.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal; 2 | 3 | import com.sun.javafx.sg.prism.NGBox; 4 | import com.sun.prism.Graphics; 5 | 6 | public class InternalNGBox extends NGBox { 7 | @Override 8 | protected void renderContent(Graphics g) { 9 | super.renderContent(MeshProxyHelper.createGraphicsProxy(g, this)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/InternalNGCylinder.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal; 2 | 3 | import com.sun.javafx.sg.prism.NGCylinder; 4 | import com.sun.prism.Graphics; 5 | 6 | public class InternalNGCylinder extends NGCylinder { 7 | @Override 8 | protected void renderContent(Graphics g) { 9 | super.renderContent(MeshProxyHelper.createGraphicsProxy(g, this)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/InternalNGMeshView.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal; 2 | 3 | import com.sun.javafx.sg.prism.NGMeshView; 4 | import com.sun.prism.Graphics; 5 | 6 | public class InternalNGMeshView extends NGMeshView { 7 | @Override 8 | protected void renderContent(Graphics g) { 9 | super.renderContent(MeshProxyHelper.createGraphicsProxy(g, this)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/InternalNGPhongMaterial.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal; 2 | 3 | import com.sun.javafx.sg.prism.NGPhongMaterial; 4 | 5 | import de.teragam.jfxshader.material.ShaderMaterial; 6 | 7 | public class InternalNGPhongMaterial extends NGPhongMaterial { 8 | 9 | private final ShaderMaterial material; 10 | 11 | public InternalNGPhongMaterial(ShaderMaterial material) { 12 | this.material = material; 13 | } 14 | 15 | public ShaderMaterial getMaterial() { 16 | return this.material; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/InternalNGSphere.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal; 2 | 3 | import com.sun.javafx.sg.prism.NGSphere; 4 | import com.sun.prism.Graphics; 5 | 6 | public class InternalNGSphere extends NGSphere { 7 | @Override 8 | protected void renderContent(Graphics g) { 9 | super.renderContent(MeshProxyHelper.createGraphicsProxy(g, this)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/MeshProxyHelper.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.lang.reflect.Proxy; 5 | 6 | import com.sun.javafx.sg.prism.NGPhongMaterial; 7 | import com.sun.javafx.sg.prism.NGShape3D; 8 | import com.sun.javafx.sg.prism.NGTriangleMesh; 9 | import com.sun.prism.Graphics; 10 | import com.sun.prism.Mesh; 11 | import com.sun.prism.MeshView; 12 | import com.sun.prism.ResourceFactory; 13 | import com.sun.prism.impl.BaseMesh; 14 | 15 | import de.teragam.jfxshader.ShaderController; 16 | import de.teragam.jfxshader.material.ShaderMaterial; 17 | import de.teragam.jfxshader.material.internal.es2.ES2ShaderMeshView; 18 | import de.teragam.jfxshader.material.internal.es2.InternalES2BasePhongMaterial; 19 | import de.teragam.jfxshader.util.Reflect; 20 | 21 | public final class MeshProxyHelper { 22 | 23 | private MeshProxyHelper() {} 24 | 25 | public static Graphics createGraphicsProxy(Graphics g, NGShape3D shape) { 26 | final NGPhongMaterial material = Reflect.on(NGShape3D.class).getFieldValue("material", shape); 27 | final MeshView meshView = Reflect.on(NGShape3D.class).getFieldValue("meshView", shape); 28 | if (!(material instanceof InternalNGPhongMaterial)) { 29 | if (meshView instanceof ShaderMeshView) { 30 | MeshProxyHelper.resetShape(shape); 31 | } 32 | return g; 33 | } else { 34 | if (ShaderController.isHLSLSupported()) { 35 | MeshProxyHelper.cleanNestedD3DMesh(shape); 36 | } 37 | if (!(meshView instanceof ShaderMeshView) && meshView != null) { 38 | MeshProxyHelper.resetShape(shape); 39 | } 40 | final Object proxyFactory = MeshProxyHelper.createResourceFactoryProxy(g.getResourceFactory(), ((InternalNGPhongMaterial) material).getMaterial()); 41 | final Object proxyGraphics = Proxy.newProxyInstance(Graphics.class.getClassLoader(), new Class[]{Graphics.class, GraphicsHelper.class}, 42 | (proxy, method, args) -> { 43 | if ("getResourceFactory".equals(method.getName())) { 44 | return proxyFactory; 45 | } 46 | if ("getRawGraphics".equals(method.getName())) { 47 | return g; 48 | } 49 | return method.invoke(g, args); 50 | }); 51 | 52 | return (Graphics) proxyGraphics; 53 | } 54 | } 55 | 56 | private static void cleanNestedD3DMesh(NGShape3D shape) { 57 | final NGTriangleMesh mesh = Reflect.on(NGShape3D.class).getFieldValue("mesh", shape); 58 | if (mesh != null) { 59 | final Mesh internalMesh = Reflect.on(NGTriangleMesh.class).getFieldValue("mesh", mesh); 60 | if (Reflect.resolveClass("com.sun.prism.d3d.D3DMesh").isInstance(internalMesh)) { 61 | internalMesh.dispose(); 62 | Reflect.on(NGTriangleMesh.class).setFieldValue("mesh", mesh, null); 63 | } 64 | } 65 | } 66 | 67 | private static void resetShape(NGShape3D shape) { 68 | final MeshView meshView = Reflect.on(NGShape3D.class).getFieldValue("meshView", shape); 69 | if (meshView != null) { 70 | meshView.dispose(); 71 | Reflect.on(NGShape3D.class).setFieldValue("meshView", shape, null); 72 | final NGTriangleMesh mesh = Reflect.on(NGShape3D.class).getFieldValue("mesh", shape); 73 | if (mesh != null) { 74 | final Mesh internalMesh = Reflect.on(NGTriangleMesh.class).getFieldValue("mesh", mesh); 75 | if (internalMesh != null) { 76 | internalMesh.dispose(); 77 | Reflect.on(NGTriangleMesh.class).setFieldValue("mesh", mesh, null); 78 | } 79 | } 80 | } 81 | final NGPhongMaterial material = Reflect.on(NGShape3D.class).getFieldValue("material", shape); 82 | if (material != null && Reflect.on(NGPhongMaterial.class).getFieldValue("material", material) != null) { 83 | Reflect.on(NGPhongMaterial.class).method("disposeMaterial").invoke(material); 84 | } 85 | } 86 | 87 | private static ResourceFactory createResourceFactoryProxy(ResourceFactory rf, ShaderMaterial material) { 88 | final InvocationHandler handler; 89 | if (ShaderController.isHLSLSupported()) { 90 | handler = (proxy, method, args) -> { 91 | if ("createMesh".equals(method.getName())) { 92 | return ShaderBaseMesh.create(rf); 93 | } 94 | if ("createMeshView".equals(method.getName())) { 95 | return ShaderMeshView.create((ShaderBaseMesh) args[0]); 96 | } 97 | if ("createPhongMaterial".equals(method.getName())) { 98 | return InternalBasePhongMaterial.create(material); 99 | } 100 | return method.invoke(rf, args); 101 | }; 102 | } else { 103 | handler = (proxy, method, args) -> { 104 | if ("createMeshView".equals(method.getName())) { 105 | return ES2ShaderMeshView.create(rf, (BaseMesh) args[0]); 106 | } 107 | if ("createPhongMaterial".equals(method.getName())) { 108 | return InternalES2BasePhongMaterial.create(rf, material); 109 | } 110 | return method.invoke(rf, args); 111 | }; 112 | } 113 | return (ResourceFactory) Proxy.newProxyInstance(ResourceFactory.class.getClassLoader(), new Class[]{ResourceFactory.class}, handler); 114 | } 115 | 116 | public interface GraphicsHelper { 117 | Graphics getRawGraphics(); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/ShaderBaseMesh.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal; 2 | 3 | import com.sun.prism.ResourceFactory; 4 | import com.sun.prism.impl.BaseMesh; 5 | 6 | import de.teragam.jfxshader.MaterialController; 7 | import de.teragam.jfxshader.material.internal.d3d.D3DBaseMeshHelper; 8 | 9 | public class ShaderBaseMesh extends BaseMesh { 10 | 11 | private static int count = 0; 12 | 13 | private final BaseMeshHelper meshHelper; 14 | 15 | protected ShaderBaseMesh(BaseMeshHelper meshHelper) { 16 | super(meshHelper); 17 | this.meshHelper = meshHelper; 18 | count++; 19 | } 20 | 21 | public BaseMeshHelper getMeshHelper() { 22 | return this.meshHelper; 23 | } 24 | 25 | public static ShaderBaseMesh create(ResourceFactory resourceFactory) { 26 | return new ShaderBaseMesh(new D3DBaseMeshHelper(MaterialController.getD3DDevice(resourceFactory))); 27 | } 28 | 29 | @Override 30 | public boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, int[] indexBufferInt, int indexBufferLength) { 31 | return this.meshHelper.buildNativeGeometry(vertexBuffer, vertexBufferLength, indexBufferInt, indexBufferLength); 32 | } 33 | 34 | @Override 35 | public boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, short[] indexBufferShort, int indexBufferLength) { 36 | return this.meshHelper.buildNativeGeometry(vertexBuffer, vertexBufferLength, indexBufferShort, indexBufferLength); 37 | } 38 | 39 | @Override 40 | public int getCount() { 41 | return ShaderBaseMesh.count; 42 | } 43 | 44 | @Override 45 | public void dispose() { 46 | this.disposerRecord.dispose(); 47 | ShaderBaseMesh.count--; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/ShaderMaterialBase.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal; 2 | 3 | import javafx.scene.paint.Material; 4 | import javafx.scene.paint.PhongMaterial; 5 | 6 | import com.sun.javafx.sg.prism.NGPhongMaterial; 7 | 8 | import de.teragam.jfxshader.material.ShaderMaterial; 9 | import de.teragam.jfxshader.util.Reflect; 10 | 11 | public class ShaderMaterialBase extends PhongMaterial { 12 | 13 | private final ShaderMaterial material; 14 | 15 | public ShaderMaterialBase(ShaderMaterial material) { 16 | this.material = material; 17 | } 18 | 19 | public void setDirty(boolean value) { 20 | Reflect.on(Material.class).method("setDirty").invoke(this, value); 21 | } 22 | 23 | public boolean isDirty() { 24 | return (boolean) Reflect.on(Material.class).method("isDirty").invoke(this); 25 | } 26 | 27 | public ShaderMaterial getJFXShaderMaterial() { 28 | return this.material; 29 | } 30 | 31 | private NGPhongMaterial peer; 32 | 33 | public NGPhongMaterial getInternalNGMaterial() { 34 | if (this.peer == null) { 35 | this.peer = new InternalNGPhongMaterial(this.material); 36 | } 37 | return this.peer; 38 | } 39 | 40 | public void updateMaterial() { 41 | this.setDirty(false); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "ShaderMaterialBase [material=" + this.material + "]"; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/ShaderMeshView.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal; 2 | 3 | import com.sun.prism.Graphics; 4 | import com.sun.prism.Material; 5 | import com.sun.prism.impl.BaseGraphics; 6 | import com.sun.prism.impl.BaseMesh; 7 | import com.sun.prism.impl.BaseMeshView; 8 | import com.sun.prism.impl.Disposer; 9 | import com.sun.prism.impl.ps.BaseShaderContext; 10 | 11 | import de.teragam.jfxshader.MaterialController; 12 | import de.teragam.jfxshader.util.Reflect; 13 | 14 | public class ShaderMeshView extends BaseMeshView { 15 | 16 | private final BaseMesh mesh; 17 | private InternalBasePhongMaterial material; 18 | private int cullingMode; 19 | private boolean wireframe; 20 | 21 | public ShaderMeshView(BaseMesh mesh, Disposer.Record disposerRecord) { 22 | super(disposerRecord); 23 | this.mesh = mesh; 24 | } 25 | 26 | public static ShaderMeshView create(BaseMesh mesh) { 27 | return new ShaderMeshView(mesh, () -> {}); 28 | } 29 | 30 | @Override 31 | public void setCullingMode(int mode) { 32 | this.cullingMode = mode; 33 | } 34 | 35 | @Override 36 | public void setMaterial(Material material) { 37 | this.material = (InternalBasePhongMaterial) material; 38 | } 39 | 40 | @Override 41 | public void setWireframe(boolean wireframe) { 42 | this.wireframe = wireframe; 43 | } 44 | 45 | @Override 46 | public void setAmbientLight(float r, float g, float b) { 47 | // Not needed 48 | } 49 | 50 | @Override 51 | public void setLight(int index, float x, float y, float z, float r, float g, float b, float w, float ca, float la, float qa, float isAttenuated, 52 | float maxRange, float dirX, float dirY, float dirZ, float innerAngle, float outerAngle, float falloff) { 53 | // Not needed 54 | } 55 | 56 | @Override 57 | public void render(Graphics g) { 58 | if (g instanceof MeshProxyHelper.GraphicsHelper) { 59 | final Graphics rawGraphics = ((MeshProxyHelper.GraphicsHelper) g).getRawGraphics(); 60 | final BaseShaderContext context = Reflect.on(BaseGraphics.class).getFieldValue("context", rawGraphics); 61 | MaterialController.getPeer(this.getMaterial().getShaderMaterial()).filter(rawGraphics, this, context); 62 | } 63 | } 64 | 65 | @Override 66 | public boolean isValid() { 67 | return true; 68 | } 69 | 70 | @Override 71 | public void dispose() { 72 | if (this.mesh != null) { 73 | this.mesh.dispose(); 74 | } 75 | this.material = null; 76 | } 77 | 78 | public BaseMesh getMesh() { 79 | return this.mesh; 80 | } 81 | 82 | public int getCullingMode() { 83 | return this.cullingMode; 84 | } 85 | 86 | public boolean isWireframe() { 87 | return this.wireframe; 88 | } 89 | 90 | public InternalBasePhongMaterial getMaterial() { 91 | return this.material; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/d3d/D3D9Types.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal.d3d; 2 | 3 | public final class D3D9Types { 4 | 5 | private D3D9Types() {} 6 | 7 | // D3DFORMAT constants 8 | public static final int D3DFMT_INDEX16 = 0x65; 9 | public static final int D3DFMT_INDEX32 = 0x66; 10 | 11 | // D3DFVF constants 12 | public static final int D3DFVF_XYZ = 0x2; 13 | public static final int D3DFVF_TEXCOUNT_SHIFT = 0x8; 14 | public static final int D3DFVF_TEXTUREFORMAT4 = 0x2; 15 | 16 | public static int D3DFVF_TEXCOORDSIZE4(int coordIndex) { 17 | return D3DFVF_TEXTUREFORMAT4 << (coordIndex * 2 + 16); 18 | } 19 | 20 | // D3DUSAGE constants 21 | public static final int D3DUSAGE_WRITEONLY = 0x8; 22 | 23 | // D3DFILLMODE constants 24 | public static final int D3DFILL_POINT = 0x1; 25 | public static final int D3DFILL_WIREFRAME = 0x2; 26 | public static final int D3DFILL_SOLID = 0x3; 27 | 28 | // D3DRENDERSTATETYPE constants 29 | public static final int D3DRS_FILLMODE = 0x8; 30 | public static final int D3DRS_CULLMODE = 0x16; 31 | 32 | // D3DPRIMITIVETYPE constants 33 | public static final int D3DPT_POINTLIST = 0x1; 34 | public static final int D3DPT_LINELIST = 0x2; 35 | public static final int D3DPT_LINESTRIP = 0x3; 36 | public static final int D3DPT_TRIANGLELIST = 0x4; 37 | public static final int D3DPT_TRIANGLESTRIP = 0x5; 38 | public static final int D3DPT_TRIANGLEFAN = 0x6; 39 | 40 | // D3DCULL constants 41 | public static final int D3DCULL_NONE = 0x1; 42 | public static final int D3DCULL_CW = 0x2; 43 | public static final int D3DCULL_CCW = 0x3; 44 | 45 | // D3DPOOL constants 46 | public static final int D3DPOOL_DEFAULT = 0x0; 47 | public static final int D3DPOOL_MANAGED = 0x1; 48 | public static final int D3DPOOL_SYSTEMMEM = 0x2; 49 | public static final int D3DPOOL_SCRATCH = 0x3; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/d3d/D3DBaseMeshHelper.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal.d3d; 2 | 3 | import de.teragam.jfxshader.material.internal.BaseMeshHelper; 4 | 5 | public final class D3DBaseMeshHelper implements BaseMeshHelper { 6 | 7 | private long vertexBufferHandle; 8 | private long indexBufferHandle; 9 | private long numVertices; 10 | private long numIndices; 11 | private final Direct3DDevice9 device; 12 | 13 | public D3DBaseMeshHelper(Direct3DDevice9 device) { 14 | this.device = device; 15 | } 16 | 17 | private static final int PRIMITIVE_VERTEX_SIZE = 36; 18 | 19 | @Override 20 | public boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, int[] indexBufferInt, int indexBufferLength) { 21 | if (!this.buildVertexBuffer(vertexBuffer, vertexBufferLength)) { 22 | return false; 23 | } 24 | 25 | final int indexSize = indexBufferLength * 4; 26 | if (this.numIndices != indexBufferLength) { 27 | this.device.releaseResource(this.indexBufferHandle); 28 | this.indexBufferHandle = this.device.createIndexBuffer(indexSize, D3D9Types.D3DUSAGE_WRITEONLY, D3D9Types.D3DFMT_INDEX32, 29 | D3D9Types.D3DPOOL_DEFAULT, 0); 30 | this.numIndices = indexBufferLength; 31 | if (this.device.getResultCode() != 0 || this.indexBufferHandle == 0) { 32 | return false; 33 | } 34 | } 35 | this.device.uploadIndexBufferDataInt(this.indexBufferHandle, indexBufferInt, indexSize); 36 | 37 | return this.device.getResultCode() == 0; 38 | } 39 | 40 | @Override 41 | public boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, short[] indexBufferShort, int indexBufferLength) { 42 | if (!this.buildVertexBuffer(vertexBuffer, vertexBufferLength)) { 43 | return false; 44 | } 45 | 46 | final int indexSize = indexBufferLength * 4; 47 | if (this.numIndices != indexBufferLength) { 48 | this.device.releaseResource(this.indexBufferHandle); 49 | this.indexBufferHandle = this.device.createIndexBuffer(indexSize, D3D9Types.D3DUSAGE_WRITEONLY, D3D9Types.D3DFMT_INDEX16, 50 | D3D9Types.D3DPOOL_DEFAULT, 0); 51 | this.numIndices = indexBufferLength; 52 | if (this.device.getResultCode() != 0 || this.indexBufferHandle == 0) { 53 | return false; 54 | } 55 | } 56 | this.device.uploadIndexBufferDataShort(this.indexBufferHandle, indexBufferShort, indexSize); 57 | 58 | return this.device.getResultCode() == 0; 59 | } 60 | 61 | @Override 62 | public void dispose() { 63 | this.device.releaseResource(this.vertexBufferHandle); 64 | if (this.device.getResultCode() == 0) { 65 | this.vertexBufferHandle = 0; 66 | } 67 | this.device.releaseResource(this.indexBufferHandle); 68 | if (this.device.getResultCode() == 0) { 69 | this.indexBufferHandle = 0; 70 | } 71 | } 72 | 73 | private boolean buildVertexBuffer(float[] vertexBuffer, int vertexBufferLength) { 74 | final int size = vertexBufferLength * 4; 75 | final int vbCount = vertexBufferLength / PRIMITIVE_VERTEX_SIZE; 76 | if (this.numVertices != vbCount) { 77 | this.device.releaseResource(this.vertexBufferHandle); 78 | final int fvf = D3D9Types.D3DFVF_XYZ | (2 << D3D9Types.D3DFVF_TEXCOUNT_SHIFT) | D3D9Types.D3DFVF_TEXCOORDSIZE4(1); 79 | this.vertexBufferHandle = this.device.createVertexBuffer(size, D3D9Types.D3DUSAGE_WRITEONLY, fvf, 80 | D3D9Types.D3DPOOL_DEFAULT, 0); 81 | this.numVertices = vbCount; 82 | if (this.device.getResultCode() != 0 || this.vertexBufferHandle == 0) { 83 | return false; 84 | } 85 | } 86 | this.device.uploadVertexBufferData(this.vertexBufferHandle, vertexBuffer, size); 87 | return this.device.getResultCode() == 0; 88 | } 89 | 90 | public long getVertexBufferHandle() { 91 | return this.vertexBufferHandle; 92 | } 93 | 94 | public long getIndexBufferHandle() { 95 | return this.indexBufferHandle; 96 | } 97 | 98 | public long getNumVertices() { 99 | return this.numVertices; 100 | } 101 | 102 | public long getNumIndices() { 103 | return this.numIndices; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/d3d/D3DShaderMaterialPeerRenderer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal.d3d; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import javafx.scene.image.Image; 8 | 9 | import com.sun.javafx.geom.transform.GeneralTransform3D; 10 | import com.sun.prism.Graphics; 11 | import com.sun.prism.Texture; 12 | import com.sun.prism.impl.ps.BaseShaderContext; 13 | 14 | import de.teragam.jfxshader.MaterialController; 15 | import de.teragam.jfxshader.exception.ShaderException; 16 | import de.teragam.jfxshader.material.internal.AbstractShaderMaterialPeerRenderer; 17 | import de.teragam.jfxshader.material.internal.ShaderBaseMesh; 18 | import de.teragam.jfxshader.material.internal.ShaderMeshView; 19 | import de.teragam.jfxshader.util.Reflect; 20 | 21 | public class D3DShaderMaterialPeerRenderer extends AbstractShaderMaterialPeerRenderer { 22 | 23 | /** 24 | * Size of a vertex in bytes. 25 | * struct PRISM_VERTEX_3D { 26 | * float x, y, z; 27 | * float tu, tv; 28 | * float nx, ny, nz, nw; 29 | * }; 30 | */ 31 | private static final int PRIMITIVE_VERTEX_SIZE = 4 * 3 + 4 * 2 + 4 * 4; 32 | 33 | @Override 34 | public void render(Graphics g, ShaderMeshView meshView, BaseShaderContext context, Map imageIndexMap) { 35 | final ShaderBaseMesh mesh = (ShaderBaseMesh) meshView.getMesh(); 36 | 37 | final Direct3DDevice9 device = MaterialController.getD3DDevice(g.getResourceFactory()); 38 | device.setFVF(D3D9Types.D3DFVF_XYZ | (2 << D3D9Types.D3DFVF_TEXCOUNT_SHIFT) | D3D9Types.D3DFVF_TEXCOORDSIZE4(1)); 39 | if (device.getResultCode() != 0) { 40 | throw new ShaderException("Failed to set FVF"); 41 | } 42 | final List textures = new ArrayList<>(); 43 | imageIndexMap.forEach((index, image) -> { 44 | final Texture texture = this.getTexture(context, image, false); 45 | device.setTexture(index, getD3DTextureHandle(texture)); 46 | if (texture != null) { 47 | textures.add(texture); 48 | } 49 | }); 50 | 51 | final int cullMode = translateCullMode(meshView.getCullingMode()); 52 | final int previousRenderState = device.getRenderState(D3D9Types.D3DRS_CULLMODE); 53 | if (previousRenderState != cullMode) { 54 | device.setRenderState(D3D9Types.D3DRS_CULLMODE, cullMode); 55 | } 56 | final int fillMode = meshView.isWireframe() ? D3D9Types.D3DFILL_WIREFRAME : D3D9Types.D3DFILL_SOLID; 57 | final int previousFillMode = device.getRenderState(D3D9Types.D3DRS_FILLMODE); 58 | if (previousFillMode != fillMode) { 59 | device.setRenderState(D3D9Types.D3DRS_FILLMODE, fillMode); 60 | } 61 | final D3DBaseMeshHelper meshHelper = (D3DBaseMeshHelper) mesh.getMeshHelper(); 62 | device.setStreamSource(0, meshHelper.getVertexBufferHandle(), 0, PRIMITIVE_VERTEX_SIZE); 63 | device.setIndices(meshHelper.getIndexBufferHandle()); 64 | device.drawIndexedPrimitive(D3D9Types.D3DPT_TRIANGLELIST, 0, 0, (int) meshHelper.getNumVertices(), 0, 65 | (int) (meshHelper.getNumIndices() / 3)); 66 | 67 | if (previousRenderState != cullMode) { 68 | device.setRenderState(D3D9Types.D3DRS_CULLMODE, previousRenderState); 69 | } 70 | if (previousFillMode != fillMode) { 71 | device.setRenderState(D3D9Types.D3DRS_FILLMODE, previousFillMode); 72 | } 73 | 74 | textures.forEach(Texture::unlock); 75 | } 76 | 77 | @Override 78 | protected float[] convertMatrix(GeneralTransform3D src) { 79 | final float[] rawMatrix = new float[16]; 80 | for (int i = 0; i < rawMatrix.length; i++) { 81 | rawMatrix[i] = (float) src.get(i); 82 | } 83 | return rawMatrix; 84 | } 85 | 86 | private static long getD3DTextureHandle(Texture texture) { 87 | if (texture == null) { 88 | return 0; 89 | } 90 | return Reflect.on("com.sun.prism.d3d.D3DTexture").method("getNativeTextureObject").invoke(texture); 91 | } 92 | 93 | private static int translateCullMode(int cullFace) { 94 | switch (cullFace) { 95 | case 0: 96 | return D3D9Types.D3DCULL_NONE; 97 | case 1: 98 | return D3D9Types.D3DCULL_CW; 99 | case 2: 100 | return D3D9Types.D3DCULL_CCW; 101 | default: 102 | throw new IllegalArgumentException("Unknown cull mode: " + cullFace); 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/d3d/D3DVertexShader.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal.d3d; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.FloatBuffer; 6 | import java.nio.IntBuffer; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | 10 | import com.sun.prism.impl.Disposer; 11 | 12 | import de.teragam.jfxshader.JFXShader; 13 | import de.teragam.jfxshader.exception.ShaderException; 14 | 15 | public class D3DVertexShader implements JFXShader { 16 | 17 | private final Direct3DDevice9 device; 18 | private final long nativeHandle; 19 | private final Map registers; 20 | 21 | private boolean valid; 22 | 23 | public D3DVertexShader(Direct3DDevice9 device, long nativeHandle, Map registers) { 24 | this.device = device; 25 | this.nativeHandle = nativeHandle; 26 | this.registers = registers; 27 | this.valid = nativeHandle != 0; 28 | if (this.valid) { 29 | Disposer.addRecord(this, this::dispose); 30 | } 31 | } 32 | 33 | public static long init(Direct3DDevice9 device, InputStream source) { 34 | try (source) { 35 | final long nativeHandle = device.createVertexShader(source.readAllBytes()); 36 | if (nativeHandle == 0) { 37 | throw new ShaderException("Failed to create vertex shader"); 38 | } 39 | return nativeHandle; 40 | } catch (IOException e) { 41 | throw new ShaderException("Failed to read vertex shader source", e); 42 | } 43 | } 44 | 45 | @Override 46 | public void enable() { 47 | this.device.setVertexShader(this.nativeHandle); 48 | this.validate(); 49 | } 50 | 51 | @Override 52 | public void disable() { 53 | this.device.setVertexShader(0); 54 | this.validate(); 55 | } 56 | 57 | @Override 58 | public boolean isValid() { 59 | return this.valid; 60 | } 61 | 62 | @Override 63 | public void setConstant(String name, int i0) { 64 | this.setConstants(name, i0); 65 | } 66 | 67 | @Override 68 | public void setConstant(String name, int i0, int i1) { 69 | this.setConstants(name, i0, i1); 70 | } 71 | 72 | @Override 73 | public void setConstant(String name, int i0, int i1, int i2) { 74 | this.setConstants(name, i0, i1, i2); 75 | } 76 | 77 | @Override 78 | public void setConstant(String name, int i0, int i1, int i2, int i3) { 79 | this.setConstants(name, i0, i1, i2, i3); 80 | } 81 | 82 | @Override 83 | public void setConstants(String name, IntBuffer buf, int off, int count) { 84 | final int[] data = new int[count * 4]; 85 | buf.get(data, off * 4, count * 4); 86 | this.device.setVertexShaderConstantI(this.getRegister(name), data, count); 87 | this.validate(); 88 | } 89 | 90 | private void setConstants(String name, int... data) { 91 | if (data.length > 0) { 92 | this.device.setVertexShaderConstantI(this.getRegister(name), data, Math.max(1, data.length / 4)); 93 | this.validate(); 94 | } 95 | } 96 | 97 | @Override 98 | public void setConstant(String name, float f0) { 99 | this.setConstants(name, f0); 100 | } 101 | 102 | @Override 103 | public void setConstant(String name, float f0, float f1) { 104 | this.setConstants(name, f0, f1); 105 | } 106 | 107 | @Override 108 | public void setConstant(String name, float f0, float f1, float f2) { 109 | this.setConstants(name, f0, f1, f2); 110 | } 111 | 112 | @Override 113 | public void setConstant(String name, float f0, float f1, float f2, float f3) { 114 | this.setConstants(name, f0, f1, f2, f3); 115 | } 116 | 117 | @Override 118 | public void setConstants(String name, FloatBuffer buf, int off, int count) { 119 | final float[] data = new float[count * 4]; 120 | buf.get(data, off * 4, count * 4); 121 | this.device.setVertexShaderConstantF(this.getRegister(name), data, count); 122 | this.validate(); 123 | } 124 | 125 | private void setConstants(String name, float... data) { 126 | if (data.length > 0) { 127 | this.device.setVertexShaderConstantF(this.getRegister(name), data, Math.max(1, data.length / 4)); 128 | this.validate(); 129 | } 130 | } 131 | 132 | @Override 133 | public void dispose() { 134 | this.valid = false; 135 | this.device.releaseResource(this.nativeHandle); 136 | this.validate(); 137 | } 138 | 139 | private void validate() { 140 | if (this.device.getResultCode() != 0) { 141 | this.valid = false; 142 | throw new ShaderException("Vertex shader operation failed"); 143 | } 144 | } 145 | 146 | private int getRegister(String name) { 147 | return Optional.ofNullable(this.registers.get(name)).orElseThrow(() -> new ShaderException("Register not found for: " + name)); 148 | } 149 | 150 | @Override 151 | public void setMatrix(String name, float[] buf, int vector4fCount) { 152 | this.device.setVertexShaderConstantF(this.getRegister(name), buf, vector4fCount); 153 | this.validate(); 154 | } 155 | 156 | @Override 157 | public Object getObject() { 158 | return this; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/d3d/Direct3DDevice9.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal.d3d; 2 | 3 | public class Direct3DDevice9 { 4 | 5 | private final long handle; 6 | 7 | // The HRESULT code of the last operation. Will be set by the native code. 8 | private int resultCode; 9 | 10 | public Direct3DDevice9(long handle) { 11 | this.handle = handle; 12 | } 13 | 14 | public long getHandle() { 15 | return this.handle; 16 | } 17 | 18 | /** 19 | * @return the HRESULT code of the last operation. 20 | */ 21 | public int getResultCode() { 22 | return this.resultCode; 23 | } 24 | 25 | public native void setFVF(int fvf); 26 | 27 | public native void setVertexShader(long pShader); 28 | 29 | public native long createVertexShader(byte[] pFunction); 30 | 31 | public native void setVertexShaderConstantF(int startRegister, float[] pConstantData, int vector4fCount); 32 | 33 | public native void setVertexShaderConstantI(int startRegister, int[] pConstantData, int vector4iCount); 34 | 35 | public native void setPixelShader(long pShader); 36 | 37 | public native void setPixelShaderConstantF(int startRegister, float[] pConstantData, int vector4fCount); 38 | 39 | public native void setTexture(int stage, long pTexture); 40 | 41 | public native void setRenderState(int state, int value); 42 | 43 | public native int getRenderState(int state); 44 | 45 | public native void setRenderTarget(int renderTargetIndex, long pRenderTarget); 46 | 47 | public native long getRenderTarget(int renderTargetIndex); 48 | 49 | public native long getSurfaceLevel(long pTexture, int level); 50 | 51 | public native void setStreamSource(int streamNumber, long pStreamData, int offsetInBytes, int stride); 52 | 53 | public native void setIndices(long pIndexData); 54 | 55 | public native void drawIndexedPrimitive(int primitiveType, int baseVertexIndex, int minVertexIndex, int numVertices, int startIndex, int primCount); 56 | 57 | public native void releaseResource(long pResource); 58 | 59 | public native long createVertexBuffer(int length, int usage, int fvf, int pool, long pSharedHandle); 60 | 61 | public native long createIndexBuffer(int length, int usage, int format, int pool, long pSharedHandle); 62 | 63 | public native void uploadVertexBufferData(long pVertexBuffer, float[] vertexBuffer, int vertexBufferLength); 64 | 65 | public native void uploadIndexBufferDataInt(long pIndexBuffer, int[] indexBufferInt, int indexBufferLength); 66 | 67 | public native void uploadIndexBufferDataShort(long pIndexBuffer, short[] indexBufferShort, int indexBufferLength); 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/es2/ES2ShaderMaterialPeerRenderer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal.es2; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import javafx.scene.image.Image; 8 | 9 | import com.sun.javafx.geom.transform.GeneralTransform3D; 10 | import com.sun.prism.Graphics; 11 | import com.sun.prism.Texture; 12 | import com.sun.prism.impl.ps.BaseShaderContext; 13 | 14 | import de.teragam.jfxshader.material.internal.AbstractShaderMaterialPeerRenderer; 15 | import de.teragam.jfxshader.material.internal.ShaderMeshView; 16 | import de.teragam.jfxshader.util.Reflect; 17 | 18 | public class ES2ShaderMaterialPeerRenderer extends AbstractShaderMaterialPeerRenderer { 19 | 20 | @Override 21 | public void render(Graphics g, ShaderMeshView meshView, BaseShaderContext context, Map imageIndexMap) { 22 | if (!(meshView instanceof ES2ShaderMeshView)) { 23 | throw new IllegalArgumentException("meshView must be an instance of ES2ShaderMeshView"); 24 | } 25 | final List textures = new ArrayList<>(); 26 | imageIndexMap.forEach((index, image) -> { 27 | final Texture texture = this.getTexture(context, image, false); 28 | Reflect.on(BaseShaderContext.class).method("updateTexture").invoke(context, index, texture); 29 | if (texture != null) { 30 | textures.add(texture); 31 | } 32 | }); 33 | final Reflect contextReflect = Reflect.on(context.getClass()); 34 | final Object glContext = contextReflect.getFieldValue("glContext", context); 35 | Reflect.on("com.sun.prism.es2.GLContext").method("renderMeshView").invoke(glContext, ((ES2ShaderMeshView) meshView).getNativeHandle()); 36 | 37 | textures.forEach(Texture::unlock); 38 | } 39 | 40 | @Override 41 | protected float[] convertMatrix(GeneralTransform3D src) { 42 | final float[] rawMatrix = new float[16]; 43 | rawMatrix[0] = (float) src.get(0); // Scale X 44 | rawMatrix[1] = (float) src.get(4); // Shear Y 45 | rawMatrix[2] = (float) src.get(8); 46 | rawMatrix[3] = (float) src.get(12); 47 | rawMatrix[4] = (float) src.get(1); // Shear X 48 | rawMatrix[5] = (float) src.get(5); // Scale Y 49 | rawMatrix[6] = (float) src.get(9); 50 | rawMatrix[7] = (float) src.get(13); 51 | rawMatrix[8] = (float) src.get(2); 52 | rawMatrix[9] = (float) src.get(6); 53 | rawMatrix[10] = (float) src.get(10); 54 | rawMatrix[11] = (float) src.get(14); 55 | rawMatrix[12] = (float) src.get(3); // Translate X 56 | rawMatrix[13] = (float) src.get(7); // Translate Y 57 | rawMatrix[14] = (float) src.get(11); 58 | rawMatrix[15] = (float) src.get(15); 59 | return rawMatrix; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/es2/ES2ShaderMeshView.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal.es2; 2 | 3 | import com.sun.prism.Material; 4 | import com.sun.prism.ResourceFactory; 5 | import com.sun.prism.impl.BaseMesh; 6 | import com.sun.prism.impl.Disposer; 7 | import com.sun.prism.impl.ps.BaseShaderContext; 8 | 9 | import de.teragam.jfxshader.exception.ShaderException; 10 | import de.teragam.jfxshader.material.internal.ShaderMeshView; 11 | import de.teragam.jfxshader.util.Reflect; 12 | 13 | public class ES2ShaderMeshView extends ShaderMeshView { 14 | 15 | private final long nativeHandle; 16 | private final BaseShaderContext es2Context; 17 | 18 | public ES2ShaderMeshView(long nativeHandle, BaseMesh mesh, Disposer.Record disposerRecord, BaseShaderContext es2Context) { 19 | super(mesh, disposerRecord); 20 | this.nativeHandle = nativeHandle; 21 | this.es2Context = es2Context; 22 | } 23 | 24 | public static ES2ShaderMeshView create(ResourceFactory rf, BaseMesh mesh) { 25 | if (!Reflect.resolveClass("com.sun.prism.es2.ES2ResourceFactory").isInstance(rf)) { 26 | throw new ShaderException("Factory is not a ES2ResourceFactory"); 27 | } 28 | final BaseShaderContext es2Context = Reflect.on(rf.getClass()).getFieldValue("context", rf); 29 | final long nativeHandle = Reflect.on(es2Context.getClass()).method("createES2MeshView").invoke(es2Context, mesh); 30 | return new ES2ShaderMeshView(nativeHandle, mesh, new ES2MeshViewDisposerRecord(es2Context, nativeHandle), es2Context); 31 | } 32 | 33 | @Override 34 | public void setCullingMode(int mode) { 35 | super.setCullingMode(mode); 36 | Reflect.on(this.es2Context.getClass()).method("setCullingMode").invoke(this.es2Context, this.nativeHandle, mode); 37 | } 38 | 39 | @Override 40 | public void setWireframe(boolean wireframe) { 41 | super.setWireframe(wireframe); 42 | Reflect.on(this.es2Context.getClass()).method("setWireframe").invoke(this.es2Context, this.nativeHandle, wireframe); 43 | } 44 | 45 | @Override 46 | public void setMaterial(Material material) { 47 | super.setMaterial(material); 48 | final Reflect contextReflect = Reflect.on(this.es2Context.getClass()); 49 | final Object glContext = contextReflect.getFieldValue("glContext", this.es2Context); 50 | Reflect.on("com.sun.prism.es2.GLContext").method("setMaterial") 51 | .invoke(glContext, this.nativeHandle, ((InternalES2BasePhongMaterial) material).getNativeHandle()); 52 | } 53 | 54 | public long getNativeHandle() { 55 | return this.nativeHandle; 56 | } 57 | 58 | public static class ES2MeshViewDisposerRecord implements Disposer.Record { 59 | 60 | private final BaseShaderContext context; 61 | private long nativeHandle; 62 | 63 | ES2MeshViewDisposerRecord(BaseShaderContext context, long nativeHandle) { 64 | this.context = context; 65 | this.nativeHandle = nativeHandle; 66 | } 67 | 68 | @Override 69 | public void dispose() { 70 | if (this.nativeHandle != 0L) { 71 | Reflect.on(this.context.getClass()).method("releaseES2MeshView").invoke(this.context, this.nativeHandle); 72 | this.nativeHandle = 0L; 73 | } 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/material/internal/es2/InternalES2BasePhongMaterial.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.material.internal.es2; 2 | 3 | import com.sun.prism.ResourceFactory; 4 | import com.sun.prism.impl.Disposer; 5 | import com.sun.prism.impl.ps.BaseShaderContext; 6 | 7 | import de.teragam.jfxshader.exception.ShaderException; 8 | import de.teragam.jfxshader.material.ShaderMaterial; 9 | import de.teragam.jfxshader.material.internal.InternalBasePhongMaterial; 10 | import de.teragam.jfxshader.util.Reflect; 11 | 12 | public class InternalES2BasePhongMaterial extends InternalBasePhongMaterial { 13 | 14 | private final long nativeHandle; 15 | 16 | public InternalES2BasePhongMaterial(long nativeHandle, ShaderMaterial material, Disposer.Record disposerRecord) { 17 | super(material, disposerRecord); 18 | this.nativeHandle = nativeHandle; 19 | } 20 | 21 | public static InternalES2BasePhongMaterial create(ResourceFactory rf, ShaderMaterial material) { 22 | if (!Reflect.resolveClass("com.sun.prism.es2.ES2ResourceFactory").isInstance(rf)) { 23 | throw new ShaderException("Factory is not a ES2ResourceFactory"); 24 | } 25 | final BaseShaderContext es2Context = Reflect.on(rf.getClass()).getFieldValue("context", rf); 26 | final long nativeHandle = Reflect.on(es2Context.getClass()).method("createES2PhongMaterial").invoke(es2Context); 27 | return new InternalES2BasePhongMaterial(nativeHandle, material, () -> { 28 | if (nativeHandle != 0L) { 29 | Reflect.on(es2Context.getClass()).method("releaseES2PhongMaterial").invoke(es2Context, nativeHandle); 30 | } 31 | }); 32 | } 33 | 34 | public long getNativeHandle() { 35 | return this.nativeHandle; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/renderstate/SubResolutionRenderState.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.renderstate; 2 | 3 | import com.sun.javafx.geom.Rectangle; 4 | import com.sun.javafx.geom.transform.BaseTransform; 5 | import com.sun.scenario.effect.impl.state.RenderState; 6 | 7 | public class SubResolutionRenderState implements RenderState { 8 | 9 | private final double resolutionDividend; 10 | 11 | public SubResolutionRenderState(double resolutionDividend) { 12 | this.resolutionDividend = resolutionDividend; 13 | } 14 | 15 | 16 | @Override 17 | public EffectCoordinateSpace getEffectTransformSpace() { 18 | return EffectCoordinateSpace.CustomSpace; 19 | } 20 | 21 | @Override 22 | public BaseTransform getInputTransform(BaseTransform filterTransform) { 23 | final BaseTransform baseTransform = filterTransform.copy(); 24 | baseTransform.setToIdentity(); 25 | baseTransform.deriveWithScale(1 / this.resolutionDividend, 1 / this.resolutionDividend, 1); 26 | return baseTransform; 27 | } 28 | 29 | @Override 30 | public BaseTransform getResultTransform(BaseTransform filterTransform) { 31 | return filterTransform.copy().deriveWithScale(this.resolutionDividend, this.resolutionDividend, 1); 32 | } 33 | 34 | @Override 35 | public Rectangle getInputClip(int i, Rectangle filterClip) { 36 | return filterClip; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/samples/effects/BlendShapes.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.samples.effects; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import javafx.beans.property.BooleanProperty; 8 | import javafx.beans.property.ObjectProperty; 9 | import javafx.geometry.Rectangle2D; 10 | 11 | import de.teragam.jfxshader.effect.EffectDependencies; 12 | import de.teragam.jfxshader.effect.TwoSamplerEffect; 13 | 14 | @EffectDependencies(BlendShapesEffectPeer.class) 15 | public class BlendShapes extends TwoSamplerEffect { 16 | 17 | private final List> shapes; 18 | private final BooleanProperty invertMask; 19 | 20 | public BlendShapes() { 21 | this.shapes = new ArrayList<>(); 22 | this.invertMask = super.createEffectBooleanProperty(false, "invertMask"); 23 | } 24 | 25 | public ObjectProperty createShapeProperty() { 26 | final ObjectProperty property = super.createEffectObjectProperty(null, "shape"); 27 | this.shapes.add(property); 28 | return property; 29 | } 30 | 31 | public List> getBlendShapes() { 32 | return Collections.unmodifiableList(this.shapes); 33 | } 34 | 35 | public boolean isInvertMask() { 36 | return this.invertMaskProperty().get(); 37 | } 38 | 39 | public BooleanProperty invertMaskProperty() { 40 | return this.invertMask; 41 | } 42 | 43 | public void setInvertMask(boolean invertMask) { 44 | this.invertMaskProperty().set(invertMask); 45 | } 46 | 47 | public static class Shape { 48 | 49 | private final Rectangle2D bounds; 50 | private final double width; 51 | private final double feather; 52 | private final double opacity; 53 | 54 | public Shape(Rectangle2D bounds, double width, double feather, double opacity) { 55 | this.bounds = bounds; 56 | this.width = width; 57 | this.feather = feather; 58 | this.opacity = opacity; 59 | } 60 | 61 | public Rectangle2D getBounds() { 62 | return this.bounds; 63 | } 64 | 65 | public double getWidth() { 66 | return this.width; 67 | } 68 | 69 | public double getFeather() { 70 | return this.feather; 71 | } 72 | 73 | public double getOpacity() { 74 | return this.opacity; 75 | } 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/samples/effects/BlendShapesEffectPeer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.samples.effects; 2 | 3 | import java.nio.FloatBuffer; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | 7 | import javafx.beans.property.ObjectProperty; 8 | import javafx.geometry.Rectangle2D; 9 | 10 | import com.sun.scenario.effect.impl.BufferUtil; 11 | 12 | import de.teragam.jfxshader.JFXShader; 13 | import de.teragam.jfxshader.ShaderDeclaration; 14 | import de.teragam.jfxshader.effect.EffectPeer; 15 | import de.teragam.jfxshader.effect.ShaderEffectPeer; 16 | import de.teragam.jfxshader.effect.ShaderEffectPeerConfig; 17 | 18 | @EffectPeer("BlendShapes") 19 | class BlendShapesEffectPeer extends ShaderEffectPeer { 20 | 21 | private FloatBuffer rects; 22 | private FloatBuffer ops; 23 | 24 | protected BlendShapesEffectPeer(ShaderEffectPeerConfig config) { 25 | super(config); 26 | } 27 | 28 | @Override 29 | protected ShaderDeclaration createShaderDeclaration() { 30 | final HashMap samplers = new HashMap<>(); 31 | samplers.put("botImg", 0); 32 | samplers.put("topImg", 1); 33 | final HashMap params = new HashMap<>(); 34 | params.put("count", 0); 35 | params.put("rects", 1); 36 | params.put("ops", 9); 37 | params.put("scale", 17); 38 | params.put("invertMask", 18); 39 | return new ShaderDeclaration(samplers, params, 40 | BlendShapes.class.getResourceAsStream("/de/teragam/jfxshader/samples/effects/blendshapes/blendshapes.frag"), 41 | BlendShapes.class.getResourceAsStream("/de/teragam/jfxshader/samples/effects/blendshapes/blendshapes.obj")); 42 | } 43 | 44 | @Override 45 | protected void updateShader(JFXShader shader, BlendShapes effect) { 46 | if (this.rects == null) { 47 | this.rects = BufferUtil.newFloatBuffer(8 * 4); 48 | this.ops = BufferUtil.newFloatBuffer(8 * 4); 49 | } 50 | this.rects.clear(); 51 | this.ops.clear(); 52 | final List> effectShapes = effect.getBlendShapes(); 53 | int effectiveCount = 0; 54 | for (int i = 0; i < Math.min(effectShapes.size(), 8); i++) { 55 | final BlendShapes.Shape shape = effectShapes.get(i).get(); 56 | if (shape != null) { 57 | effectiveCount++; 58 | final Rectangle2D bounds = shape.getBounds(); 59 | this.rects.put(new float[]{(float) bounds.getMinX(), (float) bounds.getMinY(), (float) bounds.getMaxX(), (float) bounds.getMaxY()}); 60 | this.ops.put(new float[]{(float) shape.getWidth(), (float) shape.getFeather(), (float) shape.getOpacity(), 0F}); 61 | } 62 | } 63 | for (int i = effectiveCount; i < 8; i++) { 64 | this.rects.put(new float[]{0, 0, 0, 0}); 65 | this.ops.put(new float[]{0, 0, 0, 0}); 66 | } 67 | this.rects.rewind(); 68 | this.ops.rewind(); 69 | shader.setConstant("count", effectiveCount); 70 | shader.setConstants("rects", this.rects, 0, 8); 71 | shader.setConstants("ops", this.ops, 0, 8); 72 | final double scaleX = Math.hypot(this.getTransform().getMxx(), this.getTransform().getMyx()); 73 | final double scaleY = Math.hypot(this.getTransform().getMxy(), this.getTransform().getMyy()); 74 | final double scale = Math.max(scaleX, scaleY); 75 | shader.setConstant("scale", (float) scale); // Compensation for scale transforms like dpi scaling 76 | shader.setConstant("invertMask", effect.isInvertMask() ? 1 : 0); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/samples/effects/Pixelate.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.samples.effects; 2 | 3 | import javafx.beans.property.DoubleProperty; 4 | 5 | import de.teragam.jfxshader.effect.EffectDependencies; 6 | import de.teragam.jfxshader.effect.OneSamplerEffect; 7 | 8 | @EffectDependencies(PixelateEffectPeer.class) 9 | public class Pixelate extends OneSamplerEffect { 10 | 11 | private final DoubleProperty offsetX; 12 | private final DoubleProperty offsetY; 13 | private final DoubleProperty pixelWidth; 14 | private final DoubleProperty pixelHeight; 15 | 16 | public Pixelate() { 17 | this(10, 10); 18 | } 19 | 20 | public Pixelate(double pixelWidth, double pixelHeight) { 21 | this.pixelWidth = super.createEffectDoubleProperty(pixelWidth, "pixelWidth"); 22 | this.pixelHeight = super.createEffectDoubleProperty(pixelHeight, "pixelHeight"); 23 | this.offsetX = super.createEffectDoubleProperty(0.0, "offsetX"); 24 | this.offsetY = super.createEffectDoubleProperty(0.0, "offsetY"); 25 | } 26 | 27 | public double getOffsetX() { 28 | return this.offsetX.get(); 29 | } 30 | 31 | public DoubleProperty offsetXProperty() { 32 | return this.offsetX; 33 | } 34 | 35 | public void setOffsetX(double offsetX) { 36 | this.offsetX.set(offsetX); 37 | } 38 | 39 | public double getOffsetY() { 40 | return this.offsetY.get(); 41 | } 42 | 43 | public DoubleProperty offsetYProperty() { 44 | return this.offsetY; 45 | } 46 | 47 | public void setOffsetY(double offsetY) { 48 | this.offsetY.set(offsetY); 49 | } 50 | 51 | public double getPixelWidth() { 52 | return this.pixelWidth.get(); 53 | } 54 | 55 | public DoubleProperty pixelWidthProperty() { 56 | return this.pixelWidth; 57 | } 58 | 59 | public void setPixelWidth(double pixelWidth) { 60 | this.pixelWidth.set(pixelWidth); 61 | } 62 | 63 | public double getPixelHeight() { 64 | return this.pixelHeight.get(); 65 | } 66 | 67 | public DoubleProperty pixelHeightProperty() { 68 | return this.pixelHeight; 69 | } 70 | 71 | public void setPixelHeight(double pixelHeight) { 72 | this.pixelHeight.set(pixelHeight); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/samples/effects/PixelateEffectPeer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.samples.effects; 2 | 3 | 4 | import java.util.Map; 5 | 6 | import de.teragam.jfxshader.JFXShader; 7 | import de.teragam.jfxshader.ShaderDeclaration; 8 | import de.teragam.jfxshader.effect.EffectPeer; 9 | import de.teragam.jfxshader.effect.ShaderEffectPeer; 10 | import de.teragam.jfxshader.effect.ShaderEffectPeerConfig; 11 | 12 | @EffectPeer("Pixelate") 13 | class PixelateEffectPeer extends ShaderEffectPeer { 14 | 15 | protected PixelateEffectPeer(ShaderEffectPeerConfig config) { 16 | super(config); 17 | } 18 | 19 | @Override 20 | protected ShaderDeclaration createShaderDeclaration() { 21 | final Map samplers = Map.of("baseImg", 0); 22 | final Map params = Map.of("pixelSize", 0, "offset", 1, "resolution", 2, "texCoords", 3, "viewport", 4); 23 | return new ShaderDeclaration(samplers, params, Pixelate.class.getResourceAsStream("/de/teragam/jfxshader/samples/effects/pixelate/pixelate.frag"), 24 | Pixelate.class.getResourceAsStream("/de/teragam/jfxshader/samples/effects/pixelate/pixelate.obj")); 25 | } 26 | 27 | @Override 28 | protected void updateShader(JFXShader shader, Pixelate effect) { 29 | shader.setConstant("pixelSize", Math.max((float) effect.getPixelWidth(), 1), Math.max((float) effect.getPixelHeight(), 1)); 30 | shader.setConstant("offset", (float) effect.getOffsetX(), (float) effect.getOffsetY()); 31 | shader.setConstant("resolution", (float) this.getDestNativeBounds().width, (float) this.getDestNativeBounds().height); 32 | final float[] texCoords = this.getTextureCoords(0); 33 | shader.setConstant("texCoords", texCoords[0], texCoords[1], texCoords[2], texCoords[3]); 34 | final double scaleX = Math.hypot(this.getTransform().getMxx(), this.getTransform().getMyx()); 35 | final double scaleY = Math.hypot(this.getTransform().getMxy(), this.getTransform().getMyy()); 36 | shader.setConstant("viewport", 0f, 0f, (float) scaleX, (float) scaleY); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/samples/effects/ProxyShaderEffect.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.samples.effects; 2 | 3 | import java.util.Objects; 4 | import java.util.concurrent.atomic.AtomicBoolean; 5 | import java.util.concurrent.atomic.AtomicReference; 6 | import java.util.function.BiConsumer; 7 | import java.util.function.Supplier; 8 | 9 | import javafx.application.Platform; 10 | 11 | import de.teragam.jfxshader.JFXShader; 12 | import de.teragam.jfxshader.ShaderDeclaration; 13 | import de.teragam.jfxshader.effect.EffectDependencies; 14 | import de.teragam.jfxshader.effect.EffectPeer; 15 | import de.teragam.jfxshader.effect.ShaderEffectPeer; 16 | import de.teragam.jfxshader.effect.ShaderEffectPeerConfig; 17 | import de.teragam.jfxshader.effect.TwoSamplerEffect; 18 | 19 | @EffectDependencies(ProxyShaderEffect.ProxyShaderEffectPeer.class) 20 | public class ProxyShaderEffect extends TwoSamplerEffect { 21 | 22 | private final AtomicReference> declarationSupplier; 23 | private final AtomicBoolean invalidateDeclaration; 24 | private final AtomicReference>> shaderConsumer; 25 | 26 | public ProxyShaderEffect() { 27 | this(null); 28 | } 29 | 30 | public ProxyShaderEffect(Supplier declarationSupplier) { 31 | this.declarationSupplier = new AtomicReference<>(declarationSupplier); 32 | this.invalidateDeclaration = new AtomicBoolean(); 33 | this.shaderConsumer = new AtomicReference<>(); 34 | } 35 | 36 | public void setShaderDeclarationSupplier(Supplier declarationSupplier) { 37 | this.declarationSupplier.set(declarationSupplier); 38 | } 39 | 40 | public void invalidateShaderDeclaration() { 41 | this.invalidateDeclaration.set(true); 42 | Platform.runLater(this::markDirty); 43 | } 44 | 45 | public void setShaderConsumer(BiConsumer> shaderConsumer) { 46 | this.shaderConsumer.set(shaderConsumer); 47 | } 48 | 49 | @Override 50 | public void setContinuousRendering(boolean continuousRendering) { 51 | super.setContinuousRendering(continuousRendering); 52 | } 53 | 54 | @EffectPeer(value = "ProxyShaderEffect", singleton = false) 55 | protected class ProxyShaderEffectPeer extends ShaderEffectPeer { 56 | 57 | protected ProxyShaderEffectPeer(ShaderEffectPeerConfig config) { 58 | super(config); 59 | } 60 | 61 | @Override 62 | protected ShaderDeclaration createShaderDeclaration() { 63 | return Objects.requireNonNull(ProxyShaderEffect.this.declarationSupplier.get(), "The ShaderDeclaration supplier cannot be null").get(); 64 | } 65 | 66 | @Override 67 | protected void updateShader(JFXShader shader, ProxyShaderEffect effect) { 68 | if (ProxyShaderEffect.this.invalidateDeclaration.getAndSet(false)) { 69 | this.invalidateShader(); 70 | } 71 | final BiConsumer> consumer = ProxyShaderEffect.this.shaderConsumer.get(); 72 | if (consumer != null) { 73 | consumer.accept(shader, this); 74 | } 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/samples/effects/ZoomRadialBlur.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.samples.effects; 2 | 3 | import javafx.beans.property.DoubleProperty; 4 | import javafx.beans.property.IntegerProperty; 5 | 6 | import de.teragam.jfxshader.effect.EffectDependencies; 7 | import de.teragam.jfxshader.effect.OneSamplerEffect; 8 | 9 | @EffectDependencies(ZoomRadialBlurEffectPeer.class) 10 | public class ZoomRadialBlur extends OneSamplerEffect { 11 | 12 | private final DoubleProperty strength; 13 | private final IntegerProperty blurSteps; 14 | private final DoubleProperty centerX; 15 | private final DoubleProperty centerY; 16 | 17 | public ZoomRadialBlur() { 18 | this(100.0); 19 | } 20 | 21 | public ZoomRadialBlur(double strength) { 22 | this(strength, 16); 23 | } 24 | 25 | public ZoomRadialBlur(double strength, int blurSteps) { 26 | this.strength = super.createEffectDoubleProperty(strength, "strength"); 27 | this.blurSteps = super.createEffectIntegerProperty(blurSteps, "blurSteps"); 28 | this.centerX = super.createEffectDoubleProperty(0.0, "centerX"); 29 | this.centerY = super.createEffectDoubleProperty(0.0, "centerY"); 30 | } 31 | 32 | public double getStrength() { 33 | return this.strength.get(); 34 | } 35 | 36 | public DoubleProperty strengthProperty() { 37 | return this.strength; 38 | } 39 | 40 | public void setStrength(double strength) { 41 | this.strength.set(strength); 42 | } 43 | 44 | public int getBlurSteps() { 45 | return this.blurSteps.get(); 46 | } 47 | 48 | public IntegerProperty blurStepsProperty() { 49 | return this.blurSteps; 50 | } 51 | 52 | public void setBlurSteps(int blurSteps) { 53 | this.blurSteps.set(blurSteps); 54 | } 55 | 56 | public double getCenterX() { 57 | return this.centerX.get(); 58 | } 59 | 60 | public DoubleProperty centerXProperty() { 61 | return this.centerX; 62 | } 63 | 64 | public void setCenterX(double centerX) { 65 | this.centerX.set(centerX); 66 | } 67 | 68 | public double getCenterY() { 69 | return this.centerY.get(); 70 | } 71 | 72 | public DoubleProperty centerYProperty() { 73 | return this.centerY; 74 | } 75 | 76 | public void setCenterY(double centerY) { 77 | this.centerY.set(centerY); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/samples/effects/ZoomRadialBlurEffectPeer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.samples.effects; 2 | 3 | import java.util.Map; 4 | 5 | import de.teragam.jfxshader.JFXShader; 6 | import de.teragam.jfxshader.ShaderDeclaration; 7 | import de.teragam.jfxshader.effect.EffectPeer; 8 | import de.teragam.jfxshader.effect.ShaderEffectPeer; 9 | import de.teragam.jfxshader.effect.ShaderEffectPeerConfig; 10 | 11 | @EffectPeer(value = "ZoomRadialBlur") 12 | class ZoomRadialBlurEffectPeer extends ShaderEffectPeer { 13 | 14 | protected ZoomRadialBlurEffectPeer(ShaderEffectPeerConfig config) { 15 | super(config); 16 | } 17 | 18 | @Override 19 | protected ShaderDeclaration createShaderDeclaration() { 20 | final Map samplers = Map.of("baseImg", 0); 21 | final Map params = Map.of("resolution", 0, "center", 1, "viewport", 2, "texCoords", 3, "blurSteps", 4, "strength", 5); 22 | return new ShaderDeclaration(samplers, params, 23 | ZoomRadialBlurEffectPeer.class.getResourceAsStream("/de/teragam/jfxshader/samples/effects/zoomradialblur/zoomradialblur.frag"), 24 | ZoomRadialBlurEffectPeer.class.getResourceAsStream("/de/teragam/jfxshader/samples/effects/zoomradialblur/zoomradialblur.obj")); 25 | } 26 | 27 | @Override 28 | protected void updateShader(JFXShader shader, ZoomRadialBlur effect) { 29 | shader.setConstant("resolution", (float) this.getDestBounds().width, (float) this.getDestBounds().height); 30 | shader.setConstant("center", (float) effect.getCenterX(), (float) effect.getCenterY()); 31 | final double scaleX = Math.hypot(this.getTransform().getMxx(), this.getTransform().getMyx()); 32 | final double scaleY = Math.hypot(this.getTransform().getMxy(), this.getTransform().getMyy()); 33 | shader.setConstant("viewport", 0f, 0f, (float) scaleX, (float) scaleY); 34 | final float[] texCoords = this.getTextureCoords(0); 35 | shader.setConstant("texCoords", texCoords[0], texCoords[1], texCoords[2], texCoords[3]); 36 | shader.setConstant("blurSteps", Math.max(Math.min(effect.getBlurSteps(), 64), 1)); 37 | shader.setConstant("strength", (float) effect.getStrength()); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/samples/materials/FresnelMaterial.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.samples.materials; 2 | 3 | import javafx.beans.property.DoubleProperty; 4 | import javafx.beans.property.ObjectProperty; 5 | import javafx.scene.image.Image; 6 | import javafx.scene.paint.Color; 7 | 8 | import de.teragam.jfxshader.material.MaterialDependency; 9 | import de.teragam.jfxshader.material.ShaderMaterial; 10 | 11 | @MaterialDependency(FresnelMaterialPeer.class) 12 | public class FresnelMaterial extends ShaderMaterial { 13 | 14 | private final DoubleProperty power; 15 | private final ObjectProperty glowColor; 16 | private final ObjectProperty diffuseImage; 17 | 18 | public FresnelMaterial() { 19 | this(2.0); 20 | } 21 | 22 | public FresnelMaterial(double power) { 23 | this.power = super.createMaterialDoubleProperty(power, "power"); 24 | this.glowColor = super.createMaterialObjectProperty(Color.WHITE, "diffuseColor"); 25 | this.diffuseImage = super.createMaterialImageProperty(null, "diffuseImage"); 26 | } 27 | 28 | public double getPower() { 29 | return this.powerProperty().get(); 30 | } 31 | 32 | public DoubleProperty powerProperty() { 33 | return this.power; 34 | } 35 | 36 | public void setPower(double power) { 37 | this.powerProperty().set(power); 38 | } 39 | 40 | public Image getDiffuseImage() { 41 | return this.diffuseImageProperty().get(); 42 | } 43 | 44 | public ObjectProperty diffuseImageProperty() { 45 | return this.diffuseImage; 46 | } 47 | 48 | public void setDiffuseImage(Image diffuseImage) { 49 | this.diffuseImageProperty().set(diffuseImage); 50 | } 51 | 52 | public Color getGlowColor() { 53 | return this.glowColorProperty().get(); 54 | } 55 | 56 | public ObjectProperty glowColorProperty() { 57 | return this.glowColor; 58 | } 59 | 60 | public void setGlowColor(Color glowColor) { 61 | this.glowColorProperty().set(glowColor); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/samples/materials/FresnelMaterialPeer.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.samples.materials; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import com.sun.javafx.geom.Vec3d; 7 | 8 | import de.teragam.jfxshader.JFXShader; 9 | import de.teragam.jfxshader.ShaderDeclaration; 10 | import de.teragam.jfxshader.material.ShaderMaterialPeer; 11 | 12 | public class FresnelMaterialPeer extends ShaderMaterialPeer { 13 | 14 | @Override 15 | public ShaderDeclaration createPixelShaderDeclaration() { 16 | final Map samplers = new HashMap<>(); 17 | samplers.put("diffuseImage", 0); 18 | final Map params = new HashMap<>(); 19 | params.put("color", 0); 20 | params.put("strength", 1); 21 | return new ShaderDeclaration(samplers, params, FresnelMaterialPeer.class.getResourceAsStream( 22 | "/de/teragam/jfxshader/samples/materials/fresnel/fresnel.frag"), 23 | FresnelMaterialPeer.class.getResourceAsStream("/de/teragam/jfxshader/samples/materials/fresnel/fresnel.ps.obj")); 24 | } 25 | 26 | @Override 27 | public ShaderDeclaration createVertexShaderDeclaration() { 28 | final Map params = new HashMap<>(); 29 | params.put("viewProjectionMatrix", 0); 30 | params.put("camPos", 4); 31 | params.put("worldMatrix", 35); 32 | return new ShaderDeclaration(null, params, ShaderMaterialPeer.class.getResourceAsStream("/de/teragam/jfxshader/samples/materials/fresnel/fresnel.vert"), 33 | ShaderMaterialPeer.class.getResourceAsStream("/de/teragam/jfxshader/samples/materials/fresnel/fresnel.vs.obj")); 34 | } 35 | 36 | @Override 37 | public void updateShader(JFXShader vertexShader, JFXShader pixelShader, FresnelMaterial material) { 38 | final Vec3d camPos = this.getCamera().getPositionInWorld(null); 39 | vertexShader.setConstant("camPos", (float) camPos.x, (float) camPos.y, (float) camPos.z); 40 | vertexShader.setMatrix("viewProjectionMatrix", this.getViewProjectionMatrix(), 4); 41 | vertexShader.setMatrix("worldMatrix", this.getWorldMatrix(), 4); 42 | 43 | this.setSamplerImage(material.getDiffuseImage(), 0); 44 | 45 | pixelShader.setConstant("strength", (float) material.getPower()); 46 | pixelShader.setConstant("color", (float) material.getGlowColor().getRed(), (float) material.getGlowColor().getGreen(), 47 | (float) material.getGlowColor().getBlue(), (float) material.getGlowColor().getOpacity()); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/util/ConstructorInvocationWrapper.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.util; 2 | 3 | @FunctionalInterface 4 | public interface ConstructorInvocationWrapper { 5 | T create(Object... args); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/util/MethodInvocationWrapper.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.util; 2 | 3 | @FunctionalInterface 4 | public interface MethodInvocationWrapper { 5 | T invoke(Object instance, Object... args); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/util/Reflect.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.util; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.InvocationHandler; 6 | import java.lang.reflect.Method; 7 | import java.lang.reflect.Proxy; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Objects; 13 | import java.util.Optional; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | import java.util.function.UnaryOperator; 16 | 17 | import javafx.util.Pair; 18 | 19 | import de.teragam.jfxshader.exception.ShaderException; 20 | 21 | @SuppressWarnings("unchecked") 22 | public class Reflect { 23 | 24 | private final Class clazz; 25 | 26 | private Reflect(Class clazz) { 27 | this.clazz = clazz; 28 | } 29 | 30 | public static Reflect on(Class clazz) { 31 | return new Reflect<>(clazz); 32 | } 33 | 34 | public static Reflect on(String clazzName) { 35 | return (Reflect) on(resolveClass(clazzName)); 36 | } 37 | 38 | private static final Map, String>, Field> CLASS_FIELD_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>(); 39 | private static final Map CLASS_METHOD_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>(); 40 | 41 | public Field getField(String fieldName) { 42 | return CLASS_FIELD_CONCURRENT_HASH_MAP.computeIfAbsent(new Pair<>(this.clazz, fieldName), c -> { 43 | try { 44 | final Field field = this.clazz.getDeclaredField(fieldName); 45 | field.trySetAccessible(); 46 | return field; 47 | } catch (NoSuchFieldException e) { 48 | throw new ShaderException(String.format("Could not get declared field %s of class %s", fieldName, this.clazz.getName()), e); 49 | } 50 | }); 51 | } 52 | 53 | public Method getMethod(String methodName, Class... parameterTypes) { 54 | return CLASS_METHOD_CONCURRENT_HASH_MAP.computeIfAbsent(new ReflectMethodCache(this.clazz, methodName, parameterTypes), c -> { 55 | try { 56 | final Method method = this.clazz.getDeclaredMethod(methodName, parameterTypes); 57 | method.trySetAccessible(); 58 | return method; 59 | } catch (NoSuchMethodException e) { 60 | final Optional methodOpt = Arrays.stream(this.clazz.getDeclaredMethods()).filter(m -> m.getName().equals(methodName)).findFirst(); 61 | final Method method = methodOpt.orElseThrow( 62 | () -> new ShaderException(String.format("Could not get declared method %s of class %s", methodName, this.clazz.getName()), e)); 63 | method.trySetAccessible(); 64 | return method; 65 | } 66 | }); 67 | } 68 | 69 | public T getFieldValue(String fieldName, Object instance) { 70 | try { 71 | return (T) this.getField(fieldName).get(instance); 72 | } catch (ReflectiveOperationException e) { 73 | throw new ShaderException(String.format("Could not access field %s of class %s", fieldName, this.clazz.getName()), e); 74 | } 75 | } 76 | 77 | public void setFieldValue(String fieldName, Object instance, Object value) { 78 | try { 79 | this.getField(fieldName).set(instance, value); 80 | } catch (ReflectiveOperationException e) { 81 | throw new ShaderException(String.format("Could not access field %s of class %s", fieldName, this.clazz.getName()), e); 82 | } 83 | } 84 | 85 | public void processFieldValue(String fieldName, Object instance, UnaryOperator processor) { 86 | try { 87 | final Field field = this.getField(fieldName); 88 | field.set(instance, processor.apply((T) field.get(instance))); 89 | } catch (ReflectiveOperationException e) { 90 | throw new ShaderException(String.format("Could not access field %s of class %s", fieldName, this.clazz.getName()), e); 91 | } 92 | } 93 | 94 | public MethodInvocationWrapper method(String methodName, Class... parameterTypes) { 95 | return (instance, args) -> { 96 | try { 97 | return (T) this.getMethod(methodName, parameterTypes).invoke(instance, args); 98 | } catch (ReflectiveOperationException e) { 99 | throw new ShaderException(String.format("Could not invoke method %s of class %s", methodName, this.clazz.getName()), e); 100 | } 101 | }; 102 | } 103 | 104 | public static Class resolveClass(String className) { 105 | try { 106 | return Class.forName(className); 107 | } catch (ClassNotFoundException e) { 108 | throw new ShaderException(String.format("Could not resolve class %s", className), e); 109 | } 110 | } 111 | 112 | public C allocateInstance() { 113 | final Reflect unsafeReflect = Reflect.on("sun.misc.Unsafe"); 114 | final Object unsafe = unsafeReflect.getFieldValue("theUnsafe", null); 115 | return ((MethodInvocationWrapper) unsafeReflect.method("allocateInstance", Class.class)).invoke(unsafe, this.clazz); 116 | } 117 | 118 | public boolean hasConstructor(Class... parameterTypes) { 119 | return Arrays.stream(this.clazz.getDeclaredConstructors()).anyMatch(c -> Arrays.equals(c.getParameterTypes(), parameterTypes)); 120 | } 121 | 122 | public Constructor getConstructor(Class... parameterTypes) { 123 | try { 124 | final Constructor constructor = this.clazz.getDeclaredConstructor(parameterTypes); 125 | constructor.trySetAccessible(); 126 | return constructor; 127 | } catch (NoSuchMethodException e) { 128 | throw new ShaderException(String.format("Could not get constructor of class %s", this.clazz.getName()), e); 129 | } 130 | } 131 | 132 | public ConstructorInvocationWrapper constructor(Class... parameterTypes) { 133 | return args -> { 134 | try { 135 | if (parameterTypes.length == 0 && args.length != 0) { 136 | final Class[] runtimeParameterTypes = Arrays.stream(args) 137 | .map(obj -> Objects.requireNonNull(obj, "Argument cannot be null")) 138 | .map(Object::getClass) 139 | .map(Reflect::convertToPrimitiveClass).toArray(Class[]::new); 140 | return this.getConstructor(runtimeParameterTypes).newInstance(args); 141 | } 142 | return this.getConstructor(parameterTypes).newInstance(args); 143 | } catch (ReflectiveOperationException e) { 144 | throw new ShaderException(String.format("Could not create instance of class %s", this.clazz.getName()), e); 145 | } 146 | }; 147 | } 148 | 149 | public static void addOpens(String fullyQualifiedPackageName, String module, Module currentModule) { 150 | addOpensOrExports(fullyQualifiedPackageName, module, currentModule, true); 151 | } 152 | 153 | public static void addExports(String fullyQualifiedPackageName, String module, Module currentModule) { 154 | addOpensOrExports(fullyQualifiedPackageName, module, currentModule, false); 155 | } 156 | 157 | private static void addOpensOrExports(String fullyQualifiedPackageName, String module, Module currentModule, boolean open) { 158 | try { 159 | final Optional moduleOpt = ModuleLayer.boot().findModule(module); 160 | if (moduleOpt.isEmpty()) { 161 | throw new IllegalStateException("Could not find module " + module); 162 | } 163 | final Method addOpensMethodImpl = Reflect.on(Module.class).getMethod(open ? "implAddOpens" : "implAddExports", String.class, Module.class); 164 | class OffsetProvider { 165 | int first; 166 | } 167 | final Object unsafe = Reflect.on("sun.misc.Unsafe").getFieldValue("theUnsafe", null); 168 | final long firstFieldOffset = (long) Reflect.on(unsafe.getClass()).method("objectFieldOffset", Field.class) 169 | .invoke(unsafe, OffsetProvider.class.getDeclaredField("first")); 170 | Reflect.on(unsafe.getClass()).method("putBooleanVolatile", Object.class, long.class, boolean.class) 171 | .invoke(unsafe, addOpensMethodImpl, firstFieldOffset, true); 172 | addOpensMethodImpl.invoke(moduleOpt.get(), fullyQualifiedPackageName, currentModule); 173 | } catch (ReflectiveOperationException ex) { 174 | throw new ShaderException("Could not add opens or exports", ex); 175 | } 176 | } 177 | 178 | private static Class convertToPrimitiveClass(Class clazz) { 179 | if (clazz == Integer.class) { 180 | return int.class; 181 | } else if (clazz == Long.class) { 182 | return long.class; 183 | } else if (clazz == Float.class) { 184 | return float.class; 185 | } else if (clazz == Double.class) { 186 | return double.class; 187 | } else if (clazz == Boolean.class) { 188 | return boolean.class; 189 | } else if (clazz == Byte.class) { 190 | return byte.class; 191 | } else if (clazz == Short.class) { 192 | return short.class; 193 | } else if (clazz == Character.class) { 194 | return char.class; 195 | } 196 | return clazz; 197 | } 198 | 199 | public static

P createProxy(Object object, Class methodBaseClass, Class

proxyInterface) { 200 | final Reflect reflect = Reflect.on(methodBaseClass); 201 | return createProxy(object, proxyInterface, (proxy, method, args) -> reflect.method(method.getName(), method.getParameterTypes()).invoke(object, args)); 202 | } 203 | 204 | public static

P createProxy(Object object, Class

proxyInterface, InvocationHandler invocationHandler) { 205 | final List> interfaces = new ArrayList<>(Arrays.asList(proxyInterface.getInterfaces())); 206 | if (proxyInterface.isInterface()) { 207 | interfaces.add(proxyInterface); 208 | } 209 | return (P) Proxy.newProxyInstance(proxyInterface.getClassLoader(), interfaces.toArray(new Class[0]), (proxy, method, args) -> { 210 | if ("getObject".equals(method.getName())) { 211 | return object; 212 | } else { 213 | return invocationHandler.invoke(proxy, method, args); 214 | } 215 | }); 216 | } 217 | 218 | } 219 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/util/ReflectMethodCache.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.util; 2 | 3 | import java.util.Arrays; 4 | import java.util.Objects; 5 | 6 | final class ReflectMethodCache { 7 | private final Class clazz; 8 | private final String methodName; 9 | private final Class[] parameterTypes; 10 | 11 | public ReflectMethodCache(Class clazz, String methodName, Class[] parameterTypes) { 12 | this.clazz = clazz; 13 | this.methodName = methodName; 14 | this.parameterTypes = parameterTypes; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) { 20 | return true; 21 | } 22 | if (o == null || this.getClass() != o.getClass()) { 23 | return false; 24 | } 25 | 26 | final ReflectMethodCache that = (ReflectMethodCache) o; 27 | if (!Objects.equals(this.clazz, that.clazz) || !Objects.equals(this.methodName, that.methodName)) { 28 | return false; 29 | } 30 | // Probably incorrect - comparing Object[] arrays with Arrays.equals 31 | return Arrays.equals(this.parameterTypes, that.parameterTypes); 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | int result = this.clazz != null ? this.clazz.hashCode() : 0; 37 | result = 31 * result + (this.methodName != null ? this.methodName.hashCode() : 0); 38 | result = 31 * result + Arrays.hashCode(this.parameterTypes); 39 | return result; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/de/teragam/jfxshader/util/ReflectProxy.java: -------------------------------------------------------------------------------- 1 | package de.teragam.jfxshader.util; 2 | 3 | public interface ReflectProxy { 4 | 5 | /** 6 | * @return the object that is proxied by this proxy 7 | */ 8 | Object getObject(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module de.teragam.jfxshader { 2 | requires javafx.base; 3 | requires javafx.graphics; 4 | 5 | exports de.teragam.jfxshader; 6 | exports de.teragam.jfxshader.effect; 7 | exports de.teragam.jfxshader.material; 8 | exports de.teragam.jfxshader.exception; 9 | exports de.teragam.jfxshader.renderstate; 10 | exports de.teragam.jfxshader.samples.effects; 11 | exports de.teragam.jfxshader.samples.materials; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/native-d3d/IDirect3DDevice9Wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9.h" 2 | #include 3 | 4 | jfieldID deviceField = NULL; 5 | jfieldID resultCodeField = NULL; 6 | 7 | IDirect3DDevice9* getDevice(JNIEnv *env, jobject obj) { 8 | if (deviceField == NULL) { 9 | deviceField = env->GetFieldID(env->GetObjectClass(obj), "handle", "J"); 10 | } 11 | return (IDirect3DDevice9*) env->GetLongField(obj, deviceField); 12 | } 13 | 14 | void setResultCode(JNIEnv *env, jobject obj, HRESULT resultCode) { 15 | if (resultCodeField == NULL) { 16 | resultCodeField = env->GetFieldID(env->GetObjectClass(obj), "resultCode", "I"); 17 | } 18 | env->SetIntField(obj, resultCodeField, resultCode); 19 | } 20 | 21 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setFVF(JNIEnv *env, jobject obj, jint fvf) { 22 | setResultCode(env, obj, getDevice(env, obj)->SetFVF(fvf)); 23 | } 24 | 25 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setVertexShader(JNIEnv *env, jobject obj, jlong pShader) { 26 | setResultCode(env, obj, getDevice(env, obj)->SetVertexShader((IDirect3DVertexShader9*) pShader)); 27 | } 28 | 29 | JNIEXPORT jlong JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_createVertexShader(JNIEnv *env, jobject obj, jbyteArray pFunction) { 30 | IDirect3DVertexShader9* pShader = NULL; 31 | jbyte* pFunctionBytes = env->GetByteArrayElements(pFunction, NULL); 32 | setResultCode(env, obj, getDevice(env, obj)->CreateVertexShader((DWORD*) pFunctionBytes, &pShader)); 33 | env->ReleaseByteArrayElements(pFunction, pFunctionBytes, JNI_ABORT); 34 | return (jlong) pShader; 35 | } 36 | 37 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setVertexShaderConstantF(JNIEnv *env, jobject obj, jint startRegister, jfloatArray pConstantData, jint vector4fCount) { 38 | jfloat* pConstantDataFloats = env->GetFloatArrayElements(pConstantData, NULL); 39 | setResultCode(env, obj, getDevice(env, obj)->SetVertexShaderConstantF(startRegister, (float*) pConstantDataFloats, vector4fCount)); 40 | env->ReleaseFloatArrayElements(pConstantData, pConstantDataFloats, JNI_ABORT); 41 | } 42 | 43 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setVertexShaderConstantI(JNIEnv *env, jobject obj, jint startRegister, jintArray pConstantData, jint vector4iCount) { 44 | jint* pConstantDataInts = env->GetIntArrayElements(pConstantData, NULL); 45 | setResultCode(env, obj, getDevice(env, obj)->SetVertexShaderConstantI(startRegister, (int*) pConstantDataInts, vector4iCount)); 46 | env->ReleaseIntArrayElements(pConstantData, pConstantDataInts, JNI_ABORT); 47 | } 48 | 49 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setPixelShader(JNIEnv *env, jobject obj, jlong pShader) { 50 | setResultCode(env, obj, getDevice(env, obj)->SetPixelShader((IDirect3DPixelShader9*) pShader)); 51 | } 52 | 53 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setPixelShaderConstantF(JNIEnv *env, jobject obj, jint startRegister, jfloatArray pConstantData, jint vector4fCount) { 54 | jfloat* pConstantDataFloats = env->GetFloatArrayElements(pConstantData, NULL); 55 | setResultCode(env, obj, getDevice(env, obj)->SetPixelShaderConstantF(startRegister, (float*) pConstantDataFloats, vector4fCount)); 56 | env->ReleaseFloatArrayElements(pConstantData, pConstantDataFloats, JNI_ABORT); 57 | } 58 | 59 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setTexture(JNIEnv *env, jobject obj, jint stage, jlong pTexture) { 60 | setResultCode(env, obj, getDevice(env, obj)->SetTexture(stage, (IDirect3DBaseTexture9*) pTexture)); 61 | } 62 | 63 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setRenderState(JNIEnv *env, jobject obj, jint state, jint value) { 64 | setResultCode(env, obj, getDevice(env, obj)->SetRenderState((D3DRENDERSTATETYPE) state, value)); 65 | } 66 | 67 | JNIEXPORT jint JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_getRenderState(JNIEnv *env, jobject obj, jint state) { 68 | DWORD value = 0; 69 | setResultCode(env, obj, getDevice(env, obj)->GetRenderState((D3DRENDERSTATETYPE) state, &value)); 70 | return value; 71 | } 72 | 73 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setRenderTarget(JNIEnv *env, jobject obj, jint renderTargetIndex, jlong pRenderTarget) { 74 | setResultCode(env, obj, getDevice(env, obj)->SetRenderTarget(renderTargetIndex, (IDirect3DSurface9*) pRenderTarget)); 75 | } 76 | 77 | JNIEXPORT jlong JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_getRenderTarget(JNIEnv *env, jobject obj, jint renderTargetIndex) { 78 | IDirect3DSurface9* pRenderTarget = NULL; 79 | setResultCode(env, obj, getDevice(env, obj)->GetRenderTarget(renderTargetIndex, &pRenderTarget)); 80 | return (jlong) pRenderTarget; 81 | } 82 | 83 | JNIEXPORT jlong JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_getSurfaceLevel(JNIEnv *env, jobject obj, jlong pTexture, jint level) { 84 | IDirect3DSurface9* pSurface = NULL; 85 | setResultCode(env, obj, ((IDirect3DTexture9*) pTexture)->GetSurfaceLevel(level, &pSurface)); 86 | return (jlong) pSurface; 87 | } 88 | 89 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setStreamSource(JNIEnv *env, jobject obj, jint streamNumber, jlong pStreamData, jint offsetInBytes, jint stride) { 90 | setResultCode(env, obj, getDevice(env, obj)->SetStreamSource(streamNumber, (IDirect3DVertexBuffer9*) pStreamData, offsetInBytes, stride)); 91 | } 92 | 93 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setIndices(JNIEnv *env, jobject obj, jlong pIndexData) { 94 | setResultCode(env, obj, getDevice(env, obj)->SetIndices((IDirect3DIndexBuffer9*) pIndexData)); 95 | } 96 | 97 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_drawIndexedPrimitive(JNIEnv *env, jobject obj, jint primitiveType, jint baseVertexIndex, jint minVertexIndex, jint numVertices, jint startIndex, jint primCount) { 98 | setResultCode(env, obj, getDevice(env, obj)->DrawIndexedPrimitive((D3DPRIMITIVETYPE) primitiveType, baseVertexIndex, minVertexIndex, numVertices, startIndex, primCount)); 99 | } 100 | 101 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_releaseResource(JNIEnv *env, jobject obj, jlong pResource) { 102 | if (pResource != 0) { 103 | setResultCode(env, obj, ((IUnknown*) pResource)->Release()); 104 | } 105 | } 106 | 107 | JNIEXPORT jlong JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_createVertexBuffer(JNIEnv *env, jobject obj, jint length, jint usage, jint fvf, jint pool, jlong pSharedHandle) { 108 | IDirect3DVertexBuffer9* pVertexBuffer = nullptr; 109 | setResultCode(env, obj, getDevice(env, obj)->CreateVertexBuffer(length, usage, fvf, (D3DPOOL) pool, &pVertexBuffer, nullptr)); 110 | return (jlong) pVertexBuffer; 111 | } 112 | 113 | JNIEXPORT jlong JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_createIndexBuffer(JNIEnv *env, jobject obj, jint length, jint usage, jint format, jint pool, jlong pSharedHandle) { 114 | IDirect3DIndexBuffer9* pIndexBuffer = NULL; 115 | setResultCode(env, obj, getDevice(env, obj)->CreateIndexBuffer(length, usage, (D3DFORMAT) format, (D3DPOOL) pool, &pIndexBuffer, NULL)); 116 | return (jlong) pIndexBuffer; 117 | } 118 | 119 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_uploadVertexBufferData(JNIEnv *env, jobject obj, jlong pVertexBuffer, jfloatArray pVertexData, jint vertexBufferLength) { 120 | float* vertexBuffer = (float*) (env->GetPrimitiveArrayCritical(pVertexData, 0)); 121 | void* pLockedVertexBuffer = NULL; 122 | setResultCode(env, obj, ((IDirect3DVertexBuffer9*) pVertexBuffer)->Lock(0, vertexBufferLength, &pLockedVertexBuffer, 0)); 123 | memcpy(pLockedVertexBuffer, vertexBuffer, vertexBufferLength); 124 | ((IDirect3DVertexBuffer9*) pVertexBuffer)->Unlock(); 125 | env->ReleasePrimitiveArrayCritical(pVertexData, vertexBuffer, 0); 126 | } 127 | 128 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_uploadIndexBufferDataInt(JNIEnv *env, jobject obj, jlong pIndexBuffer, jintArray pIndexData, jint indexBufferLength) { 129 | int* indexBuffer = (int*) (env->GetPrimitiveArrayCritical(pIndexData, 0)); 130 | void* pLockedIndexBuffer = NULL; 131 | setResultCode(env, obj, ((IDirect3DIndexBuffer9*) pIndexBuffer)->Lock(0, indexBufferLength, &pLockedIndexBuffer, 0)); 132 | memcpy(pLockedIndexBuffer, indexBuffer, indexBufferLength); 133 | ((IDirect3DIndexBuffer9*) pIndexBuffer)->Unlock(); 134 | env->ReleasePrimitiveArrayCritical(pIndexData, indexBuffer, 0); 135 | } 136 | 137 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_uploadIndexBufferDataShort(JNIEnv *env, jobject obj, jlong pIndexBuffer, jshortArray pIndexData, jint indexBufferLength) { 138 | short* indexBuffer = (short*) (env->GetPrimitiveArrayCritical(pIndexData, 0)); 139 | void* pLockedIndexBuffer = NULL; 140 | setResultCode(env, obj, ((IDirect3DIndexBuffer9*) pIndexBuffer)->Lock(0, indexBufferLength, &pLockedIndexBuffer, 0)); 141 | memcpy(pLockedIndexBuffer, indexBuffer, indexBufferLength); 142 | ((IDirect3DIndexBuffer9*) pIndexBuffer)->Unlock(); 143 | env->ReleasePrimitiveArrayCritical(pIndexData, indexBuffer, 0); 144 | } 145 | -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/effects/blendshapes/blendshapes.frag: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | #extension GL_OES_standard_derivatives: enable 3 | #ifdef GL_FRAGMENT_PRECISION_HIGH 4 | precision highp float; 5 | precision highp int; 6 | #else 7 | precision mediump float; 8 | precision mediump int; 9 | #endif 10 | #else 11 | #define highp 12 | #define mediump 13 | #define lowp 14 | #endif 15 | varying vec2 texCoord0; 16 | varying vec2 texCoord1; 17 | uniform vec4 jsl_pixCoordOffset; 18 | uniform sampler2D botImg; 19 | uniform sampler2D topImg; 20 | uniform int count; 21 | uniform vec4 rects[8]; 22 | uniform vec4 ops[8]; 23 | uniform float scale; 24 | uniform int invertMask; 25 | 26 | float map(float value, float min1, float max1, float min2, float max2) { 27 | return min2 + (value - min1) * (max2 - min2) / (max1 - min1); 28 | } 29 | 30 | float insideRect(vec2 p, vec4 rect, vec4 ops) { 31 | float dx = max(rect.x - p.x, max(p.x - rect.z, 0.0)); 32 | float dy = max(rect.y - p.y, max(p.y - rect.w, 0.0)); 33 | float d = sqrt(dx * dx + dy * dy); 34 | if (ops.x == 0.0) { 35 | return 1.0 - clamp(ceil(d), 0.0, 1.0); 36 | } else { 37 | return map(clamp(d / ops.x, 0.0, 1.0), ops.y, 1.0, 1.0, 0.0); 38 | } 39 | } 40 | 41 | void main() { 42 | vec2 pixcoord = vec2(gl_FragCoord.x - jsl_pixCoordOffset.x, ((jsl_pixCoordOffset.z - gl_FragCoord.y) * jsl_pixCoordOffset.w) - jsl_pixCoordOffset.y); 43 | 44 | vec4 bot = texture2D(botImg, texCoord0); 45 | vec4 top = texture2D(topImg, texCoord1); 46 | float factor = 0.0; 47 | for (int i = 0; i < count; i++) { 48 | // The scale is used to compensate for dpi scaling 49 | factor += insideRect(pixcoord / scale, rects[i], ops[i]) * ops[i].z; 50 | } 51 | factor = clamp(factor, 0.0, 1.0); 52 | if (invertMask == 1) { 53 | factor = 1.0 - factor; 54 | } 55 | gl_FragColor = bot * factor + top * (1.0 - factor); 56 | } 57 | -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/effects/blendshapes/blendshapes.hlsl: -------------------------------------------------------------------------------- 1 | sampler2D botImg : register(s0); 2 | sampler2D topImg : register(s1); 3 | int count : register(c0); 4 | float4 rects[8] : register(c1); 5 | float4 ops[8] : register(c9); 6 | float scale : register(c17); 7 | int invertMask : register(c18); 8 | 9 | float map(float value, float min1, float max1, float min2, float max2) { 10 | return min2 + (value - min1) * (max2 - min2) / (max1 - min1); 11 | } 12 | 13 | float insideRect(float2 p, float4 rect, float4 ops) { 14 | float dx = max(rect.x - p.x, max(p.x - rect.z, 0.0)); 15 | float dy = max(rect.y - p.y, max(p.y - rect.w, 0.0)); 16 | float d = sqrt(dx * dx + dy * dy); 17 | if (ops.x == 0.0) { 18 | return 1.0 - clamp(ceil(d), 0.0, 1.0); 19 | } else { 20 | return map(clamp(d / ops.x, 0.0, 1.0), ops.y, 1.0, 1.0, 0.0); 21 | } 22 | } 23 | 24 | void main(in float2 pos0 : TEXCOORD0, in float2 pos1 : TEXCOORD1, in float2 pixcoord : VPOS, in float4 jsl_vertexColor : COLOR0, out float4 color : COLOR0) { 25 | float4 bot = tex2D(botImg, pos0); 26 | float4 top = tex2D(topImg, pos1); 27 | float factor = 0.0; 28 | for (int i = 0; i < 8; i++){ 29 | if (i >= count) { 30 | break; 31 | } 32 | // The scale is used to compensate for dpi scaling 33 | factor += insideRect(pixcoord / scale, rects[i], ops[i]) * ops[i].z; 34 | } 35 | factor = clamp(factor, 0.0, 1.0); 36 | if (invertMask == 1) { 37 | factor = 1.0 - factor; 38 | } 39 | color = bot * factor + top * (1.0 - factor); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/effects/blendshapes/blendshapes.obj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teragam/JFXShader/cfe8752990f17fbcd14f68525f95d2ee74110668/src/main/resources/de/teragam/jfxshader/samples/effects/blendshapes/blendshapes.obj -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/effects/pixelate/pixelate.frag: -------------------------------------------------------------------------------- 1 | varying vec2 texCoord0; 2 | 3 | uniform sampler2D baseImg; 4 | uniform vec2 pixelSize; 5 | uniform vec2 offset; 6 | uniform vec2 resolution; 7 | uniform vec4 texCoords; 8 | uniform vec4 viewport; 9 | 10 | void main() { 11 | vec2 scaledPixelSize = (pixelSize / resolution) * viewport.zw; 12 | vec2 scaledOffset = (offset / resolution) * viewport.zw; 13 | vec2 pixelCoord = floor((texCoord0 + scaledOffset) / scaledPixelSize) * scaledPixelSize; 14 | vec2 clampedTexCoord = clamp(pixelCoord + scaledPixelSize * 0.5 + texCoords.xy, 1.0 / resolution, texCoords.zw - 1.0 / resolution); 15 | gl_FragColor = texture2D(baseImg, clampedTexCoord); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/effects/pixelate/pixelate.hlsl: -------------------------------------------------------------------------------- 1 | sampler2D baseImg : register(s0); 2 | 3 | float2 pixelSize : register(c0); 4 | float2 offset : register(c1); 5 | float2 resolution : register(c2); 6 | float4 texCoords : register(c3); 7 | float4 viewport : register(c4); 8 | 9 | void main(float2 texCoord : TEXCOORD0, out float4 color : SV_Target) { 10 | float2 scaledPixelSize = (pixelSize / resolution) * viewport.zw; 11 | float2 scaledOffset = (offset / resolution) * viewport.zw; 12 | float2 pixelCoord = floor((texCoord + scaledOffset) / scaledPixelSize) * scaledPixelSize; 13 | color = tex2D(baseImg, clamp(pixelCoord + scaledPixelSize * 0.5 + texCoords.xy, 1.0 / resolution, texCoords.zw - 1.0 / resolution)); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/effects/pixelate/pixelate.obj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teragam/JFXShader/cfe8752990f17fbcd14f68525f95d2ee74110668/src/main/resources/de/teragam/jfxshader/samples/effects/pixelate/pixelate.obj -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/effects/zoomradialblur/zoomradialblur.frag: -------------------------------------------------------------------------------- 1 | varying vec2 texCoord0; 2 | 3 | uniform sampler2D baseImg; 4 | uniform vec2 resolution; 5 | uniform vec2 center; 6 | uniform vec4 viewport; 7 | uniform vec4 texCoords; 8 | uniform int blurSteps; 9 | uniform float strength; 10 | 11 | void main() { 12 | vec2 scaledCenter = ((center / resolution + texCoords.xy) * texCoords.zw) * viewport.zw; 13 | vec2 focus = texCoord0 - scaledCenter; 14 | float calcStrength = strength * (1.0 / max(resolution.x, resolution.y)) * viewport.z; 15 | 16 | vec4 outColor = vec4(0.0, 0.0, 0.0, 0.0); 17 | 18 | for (int i = 0; i < blurSteps; i++) { 19 | float power = 1.0 - calcStrength * float(i) / float(blurSteps); 20 | vec2 texCoord = focus * power + scaledCenter; 21 | if (texCoord.x < texCoords.x || texCoord.y < texCoords.y || texCoord.x > texCoords.z || texCoord.y > texCoords.w) { 22 | break; 23 | } 24 | outColor += texture2D(baseImg, texCoord); 25 | } 26 | 27 | outColor *= 1.0 / float(blurSteps); 28 | gl_FragColor = outColor; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/effects/zoomradialblur/zoomradialblur.hlsl: -------------------------------------------------------------------------------- 1 | sampler2D baseImg : register(s0); 2 | 3 | float2 resolution : register(c0); 4 | float2 center : register(c1); 5 | float4 viewport : register(c2); 6 | float4 texCoords : register(c3); 7 | int blurSteps : register(c4); 8 | float strength : register(c5); 9 | 10 | void main(float2 pos0 : TEXCOORD0, float2 pos1 : TEXCOORD1, float2 pixcoord : VPOS, float4 jsl_vertexColor : COLOR0, out float4 color : SV_Target) { 11 | 12 | float2 scaledCenter = ((center / resolution + texCoords.xy) * texCoords.zw) * viewport.zw; 13 | float2 focus = pos0 - scaledCenter; 14 | float calcStrength = strength * (1.0 / max(resolution.x, resolution.y)) * viewport.z; 15 | 16 | float4 outColor = float4(0.0, 0.0, 0.0, 0.0); 17 | 18 | [unroll] 19 | for (int i = 0; i < 64; i++) { 20 | if (i >= blurSteps) { 21 | break; 22 | } 23 | float power = 1.0 - calcStrength * float(i) / float(blurSteps); 24 | float2 texCoord = focus * power + scaledCenter; 25 | if (texCoord.x < texCoords.x || texCoord.y < texCoords.y || texCoord.x > texCoords.z || texCoord.y > texCoords.w) { 26 | break; 27 | } 28 | outColor += tex2D(baseImg, texCoord); 29 | } 30 | 31 | outColor *= 1.0 / float(blurSteps); 32 | color = outColor; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/effects/zoomradialblur/zoomradialblur.obj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teragam/JFXShader/cfe8752990f17fbcd14f68525f95d2ee74110668/src/main/resources/de/teragam/jfxshader/samples/effects/zoomradialblur/zoomradialblur.obj -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.frag: -------------------------------------------------------------------------------- 1 | varying vec2 oTexCoords; 2 | varying vec3 eyePos; 3 | 4 | uniform sampler2D diffuseImage; 5 | uniform vec4 color; 6 | uniform float strength; 7 | 8 | void main() { 9 | vec3 n = vec3(0, 0, -1); 10 | float d = 1.0 - clamp(dot(n, normalize(eyePos)), 0.0, 1.0); 11 | d = pow(d, strength); 12 | vec4 tex = texture2D(diffuseImage, oTexCoords); 13 | gl_FragColor = tex * (1.0 - d) + color * d; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.ps.hlsl: -------------------------------------------------------------------------------- 1 | sampler diffuseImage : register(s0); 2 | float4 color : register(c0); 3 | float strength : register(c1); 4 | 5 | struct PixelInput { 6 | float4 pos : SV_POSITION; 7 | float2 oTexCoords : TEXCOORD0; 8 | float3 eyePos : TEXCOORD1; 9 | }; 10 | 11 | float4 main(PixelInput input) : SV_Target { 12 | float3 n = float3(0, 0, -1); 13 | float d = 1.0 - clamp(dot(n, normalize(input.eyePos)), 0.0, 1.0); 14 | d = pow(d, strength); 15 | float4 tex = tex2D(diffuseImage, input.oTexCoords); 16 | return tex * (1.0 - d) + color * d; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.ps.obj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teragam/JFXShader/cfe8752990f17fbcd14f68525f95d2ee74110668/src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.ps.obj -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.vert: -------------------------------------------------------------------------------- 1 | uniform mat4 viewProjectionMatrix; 2 | uniform mat4 worldMatrix; 3 | uniform vec3 camPos; 4 | uniform vec3 ambientColor; 5 | 6 | attribute vec3 pos; 7 | attribute vec2 texCoords; 8 | attribute vec4 tangent; 9 | 10 | varying vec2 oTexCoords; 11 | varying vec3 eyePos; 12 | 13 | void main() { 14 | vec3 t1 = tangent.xyz * tangent.yzx; 15 | t1 *= 2.0; 16 | vec3 t2 = tangent.zxy * tangent.www; 17 | t2 *= 2.0; 18 | vec3 t3 = tangent.xyz * tangent.xyz; 19 | t3 *= 2.0; 20 | vec3 t4 = 1.0-(t3+t3.yzx); 21 | 22 | vec3 r1 = t1 + t2; 23 | vec3 r2 = t1 - t2; 24 | 25 | mat3 sWorldMatrix = mat3(worldMatrix[0].xyz, worldMatrix[1].xyz, worldMatrix[2].xyz); 26 | eyePos = sWorldMatrix * vec3(t4.y, r1.x, r2.z); 27 | 28 | mat4 mvpMatrix = viewProjectionMatrix * worldMatrix; 29 | 30 | oTexCoords = texCoords; 31 | gl_Position = mvpMatrix * vec4(pos,1.0); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.vs.hlsl: -------------------------------------------------------------------------------- 1 | float4x4 viewProjectionMatrix : register(c0); 2 | float4 camPos : register(c4); 3 | float4x3 worldMatrix : register(c35); 4 | 5 | struct VertexInput { 6 | float4 pos : POSITION; 7 | float2 texCoords : TEXCOORD0; 8 | float4 modelVertexNormal : TEXCOORD1; 9 | }; 10 | 11 | struct VertexOutput { 12 | float4 pos : SV_POSITION; 13 | float2 oTexCoords : TEXCOORD0; 14 | float3 eyePos : TEXCOORD1; 15 | }; 16 | 17 | void quatToMatrix(float4 q, out float3 N[3]) { 18 | float3 t1 = q.xyz * q.yzx * 2; 19 | float3 t2 = q.zxy * q.www * 2; 20 | float3 t3 = q.xyz * q.xyz * 2; 21 | float3 t4 = 1 - (t3 + t3.yzx); 22 | 23 | float3 r1 = t1 + t2; 24 | float3 r2 = t1 - t2; 25 | 26 | N[0] = float3(t4.y, r1.x, r2.z); 27 | N[1] = float3(r2.x, t4.z, r1.y); 28 | N[2] = float3(r1.z, r2.y, t4.x); 29 | N[2] *= (q.w >= 0) ? 1 : -1; 30 | } 31 | 32 | VertexOutput main(VertexInput input) { 33 | VertexOutput output; 34 | 35 | float3 n[3]; 36 | quatToMatrix(input.modelVertexNormal, n); 37 | for (int i = 0; i != 3; ++i) { 38 | n[i] = mul(n[i], (float3x3) worldMatrix); 39 | } 40 | output.eyePos = n[0]; 41 | 42 | float3 worldVertexPos = mul(input.pos, worldMatrix); 43 | 44 | output.oTexCoords = input.texCoords; 45 | output.pos = mul(float4(worldVertexPos, 1.0), viewProjectionMatrix); 46 | 47 | return output; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.vs.obj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teragam/JFXShader/cfe8752990f17fbcd14f68525f95d2ee74110668/src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.vs.obj --------------------------------------------------------------------------------