From 1128349a9767d6451219d4eb2270b8a51c10fe9b Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Thu, 10 Sep 2026 17:32:26 +0200 Subject: [PATCH 1/2] fix: Compile resizer shaders with the selected NDK --- .github/workflows/build-android-release.yml | 4 ++ .../android/build.gradle | 8 +-- .../android/compile-shaders.gradle | 72 +++++++++++++++++++ .../cpp/vulkan/VulkanShaderAssetLoader.cpp | 2 +- .../package.json | 1 + 5 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 packages/react-native-vision-camera-resizer/android/compile-shaders.gradle diff --git a/.github/workflows/build-android-release.yml b/.github/workflows/build-android-release.yml index d00179f79c..b6ab43bbc5 100644 --- a/.github/workflows/build-android-release.yml +++ b/.github/workflows/build-android-release.yml @@ -6,6 +6,7 @@ on: pull_request: paths: - '.github/workflows/build-android-release.yml' + - 'packages/react-native-vision-camera-resizer/android/**' permissions: contents: write @@ -33,6 +34,9 @@ jobs: run: | ./gradlew clean :app:assembleRelease --no-daemon --no-build-cache + - name: Verify resizer shader is packaged + run: unzip -t apps/simple-camera/android/app/build/outputs/apk/release/app-release.apk assets/shaders/Resizer.comp.spv + - name: Upload APK artifact if: ${{ github.event_name == 'pull_request' }} uses: actions/upload-artifact@v7 diff --git a/packages/react-native-vision-camera-resizer/android/build.gradle b/packages/react-native-vision-camera-resizer/android/build.gradle index 4c4ae8b40f..0d1c7d9082 100644 --- a/packages/react-native-vision-camera-resizer/android/build.gradle +++ b/packages/react-native-vision-camera-resizer/android/build.gradle @@ -50,10 +50,6 @@ android { targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() - shaders { - glslcArgs.addAll(["-O"]) - } - externalNativeBuild { cmake { cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all" @@ -122,8 +118,6 @@ android { sourceSets { main { - shaders.srcDirs = ["src/main/shaders"] - if (isNewArchitectureEnabled()) { java.srcDirs += [ // React Codegen files @@ -134,6 +128,8 @@ android { } } +apply from: "./compile-shaders.gradle" + repositories { mavenCentral() google() diff --git a/packages/react-native-vision-camera-resizer/android/compile-shaders.gradle b/packages/react-native-vision-camera-resizer/android/compile-shaders.gradle new file mode 100644 index 0000000000..80f281046a --- /dev/null +++ b/packages/react-native-vision-camera-resizer/android/compile-shaders.gradle @@ -0,0 +1,72 @@ +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction +import org.gradle.process.ExecOperations + +import javax.inject.Inject + +@CacheableTask +abstract class CompileResizerShader extends DefaultTask { + // Include helper files in the input snapshot so edits to #includes recompile the shader. + @InputDirectory + @PathSensitive(PathSensitivity.RELATIVE) + abstract DirectoryProperty getShaderDirectory() + + @InputFile + @PathSensitive(PathSensitivity.NONE) + abstract RegularFileProperty getCompilerFile() + + @OutputDirectory + abstract DirectoryProperty getOutputDirectory() + + @Inject + abstract ExecOperations getExecOperations() + + @TaskAction + void compile() { + def inputFile = shaderDirectory.file("Resizer.comp").get().asFile + def outputFile = outputDirectory.file("shaders/Resizer.comp.spv").get().asFile + outputFile.parentFile.mkdirs() + execOperations.exec { + commandLine compilerFile.get().asFile, "-O", inputFile, "-o", outputFile + } + } +} + +// Own shader compilation on all AGP versions. AGP 9 disables its built-in task by +// default and requires a compiler path in the consuming app's local.properties. +android.buildFeatures.shaders = false + +def osName = System.getProperty("os.name").toLowerCase(Locale.ROOT) +def hostTag +if (osName.contains("windows")) { + hostTag = "windows-x86_64" +} else if (osName.contains("mac") || osName.contains("darwin")) { + // The NDK uses this directory on both Intel and Apple Silicon Macs. + hostTag = "darwin-x86_64" +} else if (osName.contains("linux")) { + hostTag = "linux-x86_64" +} else { + throw new GradleException("Unsupported host OS for the VisionCamera Resizer shader compiler: ${osName}") +} + +def compilerName = osName.contains("windows") ? "glslc.exe" : "glslc" +def shaderCompiler = androidComponents.sdkComponents.ndkDirectory.map { ndk -> + ndk.file("shader-tools/${hostTag}/${compilerName}") +} +def shaderSources = layout.projectDirectory.dir("src/main/shaders") + +androidComponents.onVariants(androidComponents.selector().all()) { variant -> + def compileShader = tasks.register("compile${variant.name.capitalize()}ResizerShader", CompileResizerShader) { + shaderDirectory.set(shaderSources) + compilerFile.set(shaderCompiler) + } + // AGP supplies the output directory and makes asset merging depend on this task. + variant.sources.assets.addGeneratedSourceDirectory(compileShader) { it.outputDirectory } +} diff --git a/packages/react-native-vision-camera-resizer/android/src/main/cpp/vulkan/VulkanShaderAssetLoader.cpp b/packages/react-native-vision-camera-resizer/android/src/main/cpp/vulkan/VulkanShaderAssetLoader.cpp index 4c077c645c..b96700602b 100644 --- a/packages/react-native-vision-camera-resizer/android/src/main/cpp/vulkan/VulkanShaderAssetLoader.cpp +++ b/packages/react-native-vision-camera-resizer/android/src/main/cpp/vulkan/VulkanShaderAssetLoader.cpp @@ -26,7 +26,7 @@ namespace { throw std::runtime_error("Android asset manager is not available for Vulkan shader loading."); } - // The shader is precompiled by AGP and packaged as an app asset, so native code only needs to read the SPIR-V bytes. + // The shader is precompiled by Gradle and packaged as an app asset, so native code only needs to read the SPIR-V bytes. std::unique_ptr asset(AAssetManager_open(assetManager, assetPath, AASSET_MODE_BUFFER), &AAsset_close); if (asset == nullptr) [[unlikely]] { throw std::runtime_error(std::string("Failed to open Vulkan shader asset `") + assetPath + diff --git a/packages/react-native-vision-camera-resizer/package.json b/packages/react-native-vision-camera-resizer/package.json index ef6a9856db..9f370f9c37 100644 --- a/packages/react-native-vision-camera-resizer/package.json +++ b/packages/react-native-vision-camera-resizer/package.json @@ -13,6 +13,7 @@ "lib", "nitrogen", "android/build.gradle", + "android/compile-shaders.gradle", "android/gradle.properties", "android/fix-prefab.gradle", "android/CMakeLists.txt", From 7252c60ca01358e3590aff53261805d549146119 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Thu, 10 Sep 2026 17:44:16 +0200 Subject: [PATCH 2/2] chore: Remove redundant release shader CI check --- .github/workflows/build-android-release.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/build-android-release.yml b/.github/workflows/build-android-release.yml index b6ab43bbc5..d00179f79c 100644 --- a/.github/workflows/build-android-release.yml +++ b/.github/workflows/build-android-release.yml @@ -6,7 +6,6 @@ on: pull_request: paths: - '.github/workflows/build-android-release.yml' - - 'packages/react-native-vision-camera-resizer/android/**' permissions: contents: write @@ -34,9 +33,6 @@ jobs: run: | ./gradlew clean :app:assembleRelease --no-daemon --no-build-cache - - name: Verify resizer shader is packaged - run: unzip -t apps/simple-camera/android/app/build/outputs/apk/release/app-release.apk assets/shaders/Resizer.comp.spv - - name: Upload APK artifact if: ${{ github.event_name == 'pull_request' }} uses: actions/upload-artifact@v7