1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <Mesh.h>
18 #include <SkMesh.h>
19 #include <jni.h>
20 
21 #include <utility>
22 
23 #include "BufferUtils.h"
24 #include "GraphicsJNI.h"
25 #include "graphics_jni_helpers.h"
26 
27 #define gIndexByteSize 2
28 
29 namespace android {
30 
make(JNIEnv * env,jobject,jlong meshSpec,jint mode,jobject vertexBuffer,jboolean isDirect,jint vertexCount,jint vertexOffset,jfloat left,jfloat top,jfloat right,jfloat bottom)31 static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
32                   jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top,
33                   jfloat right, jfloat bottom) {
34     auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
35     size_t bufferSize = vertexCount * skMeshSpec->stride();
36     auto buffer = copyJavaNioBufferToVector(env, vertexBuffer, bufferSize, isDirect);
37     if (env->ExceptionCheck()) {
38         return 0;
39     }
40     auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
41     auto meshPtr = new Mesh(skMeshSpec, mode, std::move(buffer), vertexCount, vertexOffset,
42                             std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
43     auto [valid, msg] = meshPtr->validate();
44     if (!valid) {
45         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
46     }
47     return reinterpret_cast<jlong>(meshPtr);
48 }
49 
makeIndexed(JNIEnv * env,jobject,jlong meshSpec,jint mode,jobject vertexBuffer,jboolean isVertexDirect,jint vertexCount,jint vertexOffset,jobject indexBuffer,jboolean isIndexDirect,jint indexCount,jint indexOffset,jfloat left,jfloat top,jfloat right,jfloat bottom)50 static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
51                          jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
52                          jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
53                          jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) {
54     auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
55     auto vertexBufferSize = vertexCount * skMeshSpec->stride();
56     auto indexBufferSize = indexCount * gIndexByteSize;
57     auto vBuf = copyJavaNioBufferToVector(env, vertexBuffer, vertexBufferSize, isVertexDirect);
58     if (env->ExceptionCheck()) {
59         return 0;
60     }
61     auto iBuf = copyJavaNioBufferToVector(env, indexBuffer, indexBufferSize, isIndexDirect);
62     if (env->ExceptionCheck()) {
63         return 0;
64     }
65     auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
66     auto meshPtr = new Mesh(skMeshSpec, mode, std::move(vBuf), vertexCount, vertexOffset,
67                             std::move(iBuf), indexCount, indexOffset,
68                             std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
69     auto [valid, msg] = meshPtr->validate();
70     if (!valid) {
71         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
72     }
73 
74     return reinterpret_cast<jlong>(meshPtr);
75 }
76 
ThrowIAEFmt(JNIEnv * env,const char * fmt,...)77 static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
78     va_list args;
79     va_start(args, fmt);
80     int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
81     va_end(args);
82     return ret;
83 }
84 
isIntUniformType(const SkRuntimeEffect::Uniform::Type & type)85 static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
86     switch (type) {
87         case SkRuntimeEffect::Uniform::Type::kFloat:
88         case SkRuntimeEffect::Uniform::Type::kFloat2:
89         case SkRuntimeEffect::Uniform::Type::kFloat3:
90         case SkRuntimeEffect::Uniform::Type::kFloat4:
91         case SkRuntimeEffect::Uniform::Type::kFloat2x2:
92         case SkRuntimeEffect::Uniform::Type::kFloat3x3:
93         case SkRuntimeEffect::Uniform::Type::kFloat4x4:
94             return false;
95         case SkRuntimeEffect::Uniform::Type::kInt:
96         case SkRuntimeEffect::Uniform::Type::kInt2:
97         case SkRuntimeEffect::Uniform::Type::kInt3:
98         case SkRuntimeEffect::Uniform::Type::kInt4:
99             return true;
100     }
101 }
102 
nativeUpdateFloatUniforms(JNIEnv * env,MeshUniformBuilder * builder,const char * uniformName,const float values[],int count,bool isColor)103 static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
104                                       const char* uniformName, const float values[], int count,
105                                       bool isColor) {
106     MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
107     if (uniform.fVar == nullptr) {
108         ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
109     } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
110         if (isColor) {
111             jniThrowExceptionFmt(
112                     env, "java/lang/IllegalArgumentException",
113                     "attempting to set a color uniform using the non-color specific APIs: %s %x",
114                     uniformName, uniform.fVar->flags);
115         } else {
116             ThrowIAEFmt(env,
117                         "attempting to set a non-color uniform using the setColorUniform APIs: %s",
118                         uniformName);
119         }
120     } else if (isIntUniformType(uniform.fVar->type)) {
121         ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
122                     uniformName);
123     } else if (!uniform.set<float>(values, count)) {
124         ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
125                     uniform.fVar->sizeInBytes(), sizeof(float) * count);
126     }
127 }
128 
updateFloatUniforms(JNIEnv * env,jobject,jlong meshWrapper,jstring uniformName,jfloat value1,jfloat value2,jfloat value3,jfloat value4,jint count)129 static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
130                                 jfloat value1, jfloat value2, jfloat value3, jfloat value4,
131                                 jint count) {
132     auto* wrapper = reinterpret_cast<Mesh*>(meshWrapper);
133     ScopedUtfChars name(env, uniformName);
134     const float values[4] = {value1, value2, value3, value4};
135     nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count, false);
136     wrapper->markDirty();
137 }
138 
updateFloatArrayUniforms(JNIEnv * env,jobject,jlong meshWrapper,jstring jUniformName,jfloatArray jvalues,jboolean isColor)139 static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
140                                      jfloatArray jvalues, jboolean isColor) {
141     auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
142     ScopedUtfChars name(env, jUniformName);
143     AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
144     nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
145                               autoValues.length(), isColor);
146     wrapper->markDirty();
147 }
148 
nativeUpdateIntUniforms(JNIEnv * env,MeshUniformBuilder * builder,const char * uniformName,const int values[],int count)149 static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
150                                     const char* uniformName, const int values[], int count) {
151     MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
152     if (uniform.fVar == nullptr) {
153         ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
154     } else if (!isIntUniformType(uniform.fVar->type)) {
155         ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
156                     uniformName);
157     } else if (!uniform.set<int>(values, count)) {
158         ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
159                     uniform.fVar->sizeInBytes(), sizeof(float) * count);
160     }
161 }
162 
updateIntUniforms(JNIEnv * env,jobject,jlong meshWrapper,jstring uniformName,jint value1,jint value2,jint value3,jint value4,jint count)163 static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
164                               jint value1, jint value2, jint value3, jint value4, jint count) {
165     auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
166     ScopedUtfChars name(env, uniformName);
167     const int values[4] = {value1, value2, value3, value4};
168     nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count);
169     wrapper->markDirty();
170 }
171 
updateIntArrayUniforms(JNIEnv * env,jobject,jlong meshWrapper,jstring uniformName,jintArray values)172 static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
173                                    jintArray values) {
174     auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
175     ScopedUtfChars name(env, uniformName);
176     AutoJavaIntArray autoValues(env, values, 0);
177     nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
178                             autoValues.length());
179     wrapper->markDirty();
180 }
181 
MeshWrapper_destroy(Mesh * wrapper)182 static void MeshWrapper_destroy(Mesh* wrapper) {
183     delete wrapper;
184 }
185 
getMeshFinalizer(JNIEnv *,jobject)186 static jlong getMeshFinalizer(JNIEnv*, jobject) {
187     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
188 }
189 
190 static const JNINativeMethod gMeshMethods[] = {
191         {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
192         {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make},
193         {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J",
194          (void*)makeIndexed},
195         {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
196         {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
197         {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
198         {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
199 
register_android_graphics_Mesh(JNIEnv * env)200 int register_android_graphics_Mesh(JNIEnv* env) {
201     android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
202     return 0;
203 }
204 
205 }  // namespace android