91 Commits
Author SHA1 Message Date
xsl 96c24da077 添加双纹理采样 2025-10-22 18:13:10 +08:00
xsl ee9e70c53a 赴叶跟新的第二版 2025-10-19 22:17:04 +08:00
xsl bfb061d88a 傅叶跟新的第二版 2025-10-19 22:15:07 +08:00
xsl e85f78ece2 发版本更新资源 2025-10-15 22:07:24 +08:00
xsl e6f7d1efb3 完成新模型输入,发延时优化版本。 2025-10-15 22:02:49 +08:00
xsl 4a45c4dd43 更新资源文件, 2025-10-15 17:10:19 +08:00
xsl ca4bb5c34b 添加了顶点匹配脚本生成 2025-10-15 17:09:42 +08:00
xsl 42cd8265f5 1、修改效果切换名称为demo0
2、1秒钟后无检测点就不画线,添加了平台判断
3、添加了背面剔除判断
2025-10-15 17:08:09 +08:00
xsl fd25180989 添加原始的face_picture.obj 2025-10-15 11:01:35 +08:00
xsl b55d4b9600 添加背面剔除模式。 2025-10-15 10:06:28 +08:00
xsl 463b4724c9 修改背景暗,改为非srgb模式。 2025-10-15 09:28:25 +08:00
xsl 5e851505bb 删除video检测的代码,添加延时输出的代码。 2025-10-14 17:17:29 +08:00
xsl 5b9ca62976 加入人脸真实纹理 2025-10-01 19:13:17 +08:00
xsl 346de14e00 加入纹理创建的srgb 2025-10-01 19:11:52 +08:00
xsl 8d1f34604e 真人照片纹理 2025-10-01 19:10:47 +08:00
xsl 3f4dcfa0a7 图片的obj生成 2025-10-01 18:56:26 +08:00
xsl f64e345e9b 发版本 2025-09-29 23:42:16 +08:00
xsl 2e83be8414 发版本 2025-09-29 23:41:51 +08:00
xsl e7b5f24cf7 发版本了v2 2025-09-29 23:40:22 +08:00
xsl f4665a4c9f 放到android 包里面 2025-09-29 23:23:16 +08:00
xsl 72f21a21af 放到android 包里面 2025-09-29 23:22:34 +08:00
xsl f677d522b4 更新纹理 2025-09-29 23:07:41 +08:00
xsl 13d3372fc4 打包通过 2025-09-29 22:53:48 +08:00
xsl 55b9c00292 覆盖input 函数 2025-09-29 18:02:56 +08:00
xsl 7b41c64a59 添加新模型 2025-09-29 17:48:07 +08:00
xsl bf43130c0d 添加点 2025-09-29 16:18:16 +08:00
xsl 65c561c440 完成 2025-09-28 22:20:46 +08:00
xsl d6818e8816 完成 2025-09-28 22:20:28 +08:00
xsl cbe0056ab2 完成 2025-09-28 22:20:13 +08:00
xsl 9aa28ca830 windows 复现问题。 2025-09-28 21:29:27 +08:00
xsl db128c8f49 windows 复现问题。 2025-09-28 21:29:11 +08:00
xsl 3e33975e2a 添加顶点延长。 2025-09-28 18:20:50 +08:00
xsl 86889c334e 完成UV对齐 2025-09-28 17:18:58 +08:00
xsl 0db09f12c3 完成UV对齐 2025-09-28 17:18:37 +08:00
xsl 481bdefa74 asdf 2025-09-27 23:17:38 +08:00
xsl 41adba285d asdf 2025-09-27 23:17:21 +08:00
xsl 3f7faff423 asdf 2025-09-27 22:17:10 +08:00
xsl 4721726618 asdf 2025-09-27 22:16:53 +08:00
xsl aa9a524c33 asdf 2025-09-27 21:37:26 +08:00
xsl 1d1a1a4907 asdf 2025-09-27 21:27:53 +08:00
xsl 58f8c42b04 阿斯蒂芬 2025-09-27 20:58:57 +08:00
xsl 701187ec11 加入所有顶点 2025-09-27 20:05:03 +08:00
xsl cb7b600eda 查找相同的顶点 2025-09-27 18:44:23 +08:00
xsl e7834d7032 asdf 2025-09-27 11:45:32 +08:00
xsl f7fd225bc6 阿斯蒂芬 2025-09-27 11:44:43 +08:00
xsl dfb973bed0 android 设备上跑通透明 2025-09-26 21:52:54 +08:00
xsl 326593eb4f 完成透明的渲染 2025-09-26 18:21:06 +08:00
xsl 57ac3a674c 调整图片大小 2025-09-26 15:21:16 +08:00
xsl db5fb1e04a add assets 2025-09-26 15:20:20 +08:00
xsl c17ac7e6b5 推送常量完成。 2025-09-26 15:10:10 +08:00
xsl 3c0dda0e28 推送常量完成。 2025-09-26 15:09:51 +08:00
xsl 7d3427e4c0 添加法线 2025-09-26 12:21:54 +08:00
xsl 75c20ecfed utf-8 编码 2025-09-26 10:56:17 +08:00
xsl 5455da4a57 加入顶点注释和面注释 2025-09-26 10:51:12 +08:00
xsl 67404122fb 阿斯蒂芬 2025-09-25 22:36:32 +08:00
xsl 1a621bd5bf 阿斯蒂芬 2025-09-25 22:35:41 +08:00
xsl 8d195515c3 python 生成模型 2025-09-25 22:34:34 +08:00
xsl a9239fc39f 点和线 2025-09-25 21:51:58 +08:00
xsl 8dc5aab8fe 完成背景的推送 2025-09-24 23:23:39 +08:00
xsl c711a90592 完成背景的推送。 2025-09-24 23:23:16 +08:00
xsl 41081bf62d 正交投影完成 2025-09-24 22:20:45 +08:00
xsl acd9eea405 正交投影完成 2025-09-24 22:20:22 +08:00
xsl a112cecfbf 正交投影完成 2025-09-24 22:20:07 +08:00
xsl ceba7f90ac 添加复制 2025-09-23 20:04:23 +08:00
xsl 7ff02db939 添加复制 2025-09-23 20:04:09 +08:00
xsl 599e041732 添加复制的资源 2025-09-23 20:03:06 +08:00
xsl 48517e685d 第一个版本 2025-09-22 23:33:47 +08:00
xsl a64a6715ee 第一个版本 2025-09-22 23:33:31 +08:00
xsl 847918884e 第一个版本 2025-09-22 23:33:07 +08:00
xsl 2acce4ad70 阿斯蒂芬 2025-09-22 22:20:49 +08:00
xsl b5f1e076a0 save code 2025-09-22 22:20:29 +08:00
xsl 0318224673 画出虚线来了 2025-09-22 21:08:13 +08:00
xsl 610537fe9d 实线画出来了。 2025-09-22 19:14:14 +08:00
xsl cb59a5c1c8 实线画出来了。 2025-09-22 19:13:48 +08:00
xsl f7d41908dc 保存一下代码。 2025-09-22 18:52:03 +08:00
xsl e1f4c01ce6 虚线的代码完成。 2025-09-22 18:12:15 +08:00
xsl 526e8d22f2 虚线的代码完成。 2025-09-22 18:11:41 +08:00
xsl 3207c47dd2 虚线的代码完成。 2025-09-22 18:10:55 +08:00
xsl 60afc59582 虚线的代码完成。 2025-09-22 18:10:34 +08:00
xsl 12b26a0555 画图,画点,画线 2025-09-22 15:17:10 +08:00
xsl f36ec52f4a 画图,画点,画线 2025-09-22 15:16:40 +08:00
xsl 4f0be3ecc1 加入图片纹理和点数据 2025-09-22 13:55:37 +08:00
xsl 1070b2f098 加入了,png图片加载为纹理。 json 读取点坐标用于调试 2025-09-22 13:54:51 +08:00
xsl 51616af8ce 阿斯蒂芬 2025-09-22 00:33:03 +08:00
xsl ded9642c4d 阿斯蒂芬 2025-09-22 00:32:26 +08:00
xsl 86e903fc86 asdf 2025-09-22 00:32:03 +08:00
xsl 628597fe59 终于把线画出来了。 2025-09-21 21:50:28 +08:00
xsl 365387b8d9 保存代码 2025-09-21 20:53:30 +08:00
xsl 09b7bddc84 至少点是画出来了。 2025-09-21 20:39:52 +08:00
xsl cf07788d74 画线的代码写完 2025-09-21 19:05:18 +08:00
xsl 8c39579e1c remove other sample and change apk to inewme 2025-09-21 17:18:31 +08:00
1503 changed files with 128418 additions and 123110 deletions
+3 -1
View File
@@ -20,8 +20,10 @@ android {
externalNativeBuild {
cmake {
abiFilters.addAll( 'arm64-v8a', 'x86_64')
abiFilters.addAll( 'arm64-v8a')
arguments '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=c++_static', '-DVKB_VALIDATION_LAYERS=OFF'
//arguments '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=c++_static', '-DVKB_VALIDATION_LAYERS=OFF', "-DCMAKE_BUILD_TYPE=Release"
//cppFlags "-DRELEASE_MODE"
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

@@ -0,0 +1,120 @@
{
"accessors" : [
{
"bufferView" : 0,
"componentType" : 5126,
"count" : 24,
"type" : "VEC3"
},
{
"bufferView" : 1,
"componentType" : 5126,
"count" : 24,
"max" : [
1,
1,
1
],
"min" : [
-1,
-1,
-1
],
"type" : "VEC3"
},
{
"bufferView" : 2,
"componentType" : 5126,
"count" : 24,
"type" : "VEC4"
},
{
"bufferView" : 3,
"componentType" : 5126,
"count" : 24,
"type" : "VEC2"
},
{
"bufferView" : 4,
"componentType" : 5121,
"count" : 36,
"type" : "SCALAR"
}
],
"asset" : {
"generator" : "Khronos Blender glTF 2.0 I/O",
"version" : "2.0"
},
"bufferViews" : [
{
"buffer" : 0,
"byteLength" : 288,
"byteOffset" : 0
},
{
"buffer" : 0,
"byteLength" : 288,
"byteOffset" : 288
},
{
"buffer" : 0,
"byteLength" : 384,
"byteOffset" : 576
},
{
"buffer" : 0,
"byteLength" : 192,
"byteOffset" : 960
},
{
"buffer" : 0,
"byteLength" : 36,
"byteOffset" : 1152
}
],
"buffers" : [
{
"byteLength" : 1188,
"uri" : "data:application/octet-stream;base64,AACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAgAAAAAAAAIC/AAAAgAAAAAAAAIC/AAAAgAAAAAAAAIC/AACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAAAAgAAAAIAAAIA/AAAAgAAAAIAAAIA/AAAAgAAAAIAAAIA/AAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AACAPwAAAIAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgL8AAACAAAAAAAAAgD8AAACAAACAvwAAgD8AAIC/AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAPwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAPwAAgD8AAIA/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAvwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAvwAAgL8AAIC/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAPwAAgD8AAIC/AACAvwAAgD8AAIA/AACAPwAAgD8AAIA/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAPwAAgL8AAIC/AACAvwAAgD8AAIC/AAAAAAAAAAAAAIC/AACAvwAAAAAAAAAAAACAvwAAgL8AAAAAAAAAAAAAgL8AAIC/AACAPwAAAAAAAACAAACAvwAAgD8AAAAAAAAAgAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgL8AAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAvwAAgD8AAAAAAAAAgAAAgL8AAIA/AAAAAAAAAIAAAIC/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AAAAAAAAAAAAAIC/AACAvwAAgD8AAAAAAAAAgAAAgL8AAAAAAAAAAAAAgL8AAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgL8AAIA/AAAAAAAAAIAAAIA/AAAAAAAAgD8AAIC/AAAAQAAAgL8AAIA/AAAAAAAAgD8AAIC/AAAAQAAAgL8AAIA/AAAAAAAAgD8AAIA/AAAAQAAAgD8AAIA/AAAAAAAAgD8AAIA/AAAAQAAAgD8AAIA/AAAAAAAAgD8AAIA/AAAAQAAAAAAAAABAAAAAAAAAgD8AAIC/AAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAIA/AACAPwAAgL8AAIA/AAECAwQFBgcICQoLDA0ODxARABIBAxMEBhQHCRUKDBYNDxcQ"
}
],
"extensions" : {},
"materials" : [
{
"name" : "Material",
"pbrMetallicRoughness" : {}
}
],
"meshes" : [
{
"name" : "Cube.001",
"primitives" : [
{
"attributes" : {
"NORMAL" : 0,
"POSITION" : 1,
"TANGENT" : 2,
"TEXCOORD_0" : 3
},
"indices" : 4,
"material" : 0
}
]
}
],
"nodes" : [
{
"mesh" : 0,
"name" : "Cube"
}
],
"scene" : 0,
"scenes" : [
{
"name" : "Scene",
"nodes" : [
0
]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,34 @@
////
- Copyright (c) 2024-2025, Sascha Willems
-
- SPDX-License-Identifier: Apache-2.0
-
- Licensed under the Apache License, Version 2.0 the "License";
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
////
= Shaders
== Shader languages
This folder contains the textual shaders for the samples. All samples come with GLSL shaders and some optionally with HLSL shaders. For samples that support both shader language this is a good way to compare GLSL to HLSL syntax when targeting SPIR-V for Vulkan.
== Compiling shaders
The samples load offline compiled SPIR-V variants of the GLSL/HLSL shaders. If you have the appropriate compiler installed, e.g. via the LunarG VUlkan SDK, shaders will be automatically compiled when building the samples project.
== Further information
The xref:guide:ROOT:index.adoc[Vulkan Guide] contains further information on how to use HLSL with Vulkan and how it compares to GLSL:
* xref:guide::hlsl.adoc[HLSL in Vulkan]
* xref:guide::high_level_shader_language_comparison.adoc[High level shading language comparison]
@@ -16,8 +16,6 @@
* limitations under the License.
*/
#define MAX_LIGHT_COUNT 8
precision highp float;
layout(set = 0, binding = 0) uniform sampler2D base_color_texture;
@@ -28,21 +26,31 @@ layout(location = 2) in vec3 in_normal;
layout(location = 0) out vec4 o_color;
layout(set = 0, binding = 1) uniform MVPUniform
layout(set = 0, binding = 1) uniform GlobalUniform
{
mat4 model;
mat4 view_proj;
mat4 scale;
mat4 padding;
} mvp_uniform;
vec3 camera_position;
}
global_uniform;
// Push constants come with a limitation in the size of data.
// The standard requires at least 128 bytes
layout(push_constant, std430) uniform PBRMaterialUniform
{
vec4 base_color_factor;
float metallic_factor;
float roughness_factor;
}
pbr_material_uniform;
#include "lighting.h"
layout(set = 0, binding = 4) uniform LightsInfo
{
Light directional_lights[MAX_LIGHT_COUNT];
Light point_lights[MAX_LIGHT_COUNT];
Light spot_lights[MAX_LIGHT_COUNT];
Light directional_lights[48];
Light point_lights[48];
Light spot_lights[48];
}
lights_info;
@@ -15,11 +15,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
precision mediump float;
layout (location = 0) out vec2 outUV;
layout (binding = 0) uniform sampler2D fontSampler;
layout (location = 0) in vec2 inUV;
layout (location = 1) in vec4 inColor;
layout (location = 0) out vec4 outColor;
void main()
{
outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
outColor = inColor * texture(fontSampler, inUV);
}
Binary file not shown.
@@ -1,5 +1,5 @@
#version 450
/* Copyright (c) 2021-2024, Sascha Willems
#version 320 es
/* Copyright (c) 2019, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
@@ -16,19 +16,27 @@
* limitations under the License.
*/
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec2 inUV;
precision mediump float;
layout (binding = 0) uniform UBO
{
mat4 projection;
mat4 model;
} ubo;
layout (location = 0) in vec2 inPos;
layout (location = 1) in vec2 inUV;
layout (location = 2) in vec4 inColor;
layout (push_constant) uniform PushConstants {
mat4 transform;
} pushConstants;
layout (location = 0) out vec2 outUV;
layout (location = 1) out vec4 outColor;
out gl_PerVertex
{
vec4 gl_Position;
};
void main()
{
outUV = inUV;
gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
outColor = inColor;
gl_Position = pushConstants.transform * vec4(inPos.xy, 0.0, 1.0);
}
Binary file not shown.
@@ -0,0 +1,52 @@
/* Copyright (c) 2020-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
struct Light
{
vec4 position; // position.w represents type of light
vec4 color; // color.w represents light intensity
vec4 direction; // direction.w represents range
vec2 info; // (only used for spot lights) info.x represents light inner cone angle, info.y represents light outer cone angle
};
vec3 apply_directional_light(Light light, vec3 normal)
{
vec3 world_to_light = -light.direction.xyz;
world_to_light = normalize(world_to_light);
float ndotl = clamp(dot(normal, world_to_light), 0.0, 1.0);
return ndotl * light.color.w * light.color.rgb;
}
vec3 apply_point_light(Light light, vec3 pos, vec3 normal)
{
vec3 world_to_light = light.position.xyz - pos;
float dist = length(world_to_light) * 0.005;
float atten = 1.0 / (dist * dist);
world_to_light = normalize(world_to_light);
float ndotl = clamp(dot(normal, world_to_light), 0.0, 1.0);
return ndotl * light.color.w * atten * light.color.rgb;
}
vec3 apply_spot_light(Light light, vec3 pos, vec3 normal)
{
vec3 light_to_pixel = normalize(pos - light.position.xyz);
float theta = dot(light_to_pixel, normalize(light.direction.xyz));
float inner_cone_angle = light.info.x;
float outer_cone_angle = light.info.y;
float intensity = (theta - outer_cone_angle) / (inner_cone_angle - outer_cone_angle);
return smoothstep(0.0, 1.0, intensity) * light.color.w * light.color.rgb;
}
@@ -1,5 +1,5 @@
#version 450
/* Copyright (c) 2023-2024, Sascha Willems
/* Copyright (c) 2022-2024, Sascha Willems
*
* SPDX-License-Identifier: Apache-2.0
*
@@ -16,13 +16,16 @@
* limitations under the License.
*/
layout (set = 2, binding = 0) uniform sampler2D samplerColorMap;
#extension GL_EXT_nonuniform_qualifier : require
layout (set = 0, binding = 1) uniform sampler2D textures[];
layout (location = 0) in vec2 inUV;
layout (location = 1) flat in int inTexIndex;
layout (location = 0) out vec4 outFragColor;
void main()
{
outFragColor = texture(samplerColorMap, inUV);
outFragColor = texture(textures[nonuniformEXT(inTexIndex)], inUV);
}
@@ -1,5 +1,5 @@
#version 450
/* Copyright (c) 2023-2024, Sascha Willems
/* Copyright (c) 2022-2024, Sascha Willems
*
* SPDX-License-Identifier: Apache-2.0
*
@@ -18,19 +18,20 @@
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec2 inUV;
layout (location = 2) in vec3 inNormal;
layout (location = 2) in int inTexIndex;
layout (binding = 0) uniform UBO
{
mat4 projection;
mat4 model;
vec4 viewPos;
mat4 view;
} ubo;
layout (location = 0) out vec2 outUV;
layout (location = 1) flat out int outTexIndex;
void main()
{
outUV = inUV;
gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
outTexIndex = inTexIndex;
gl_Position = ubo.projection * ubo.view * vec4(inPos.xyz, 1.0);
}
@@ -16,17 +16,18 @@
*/
[[vk::binding(1, 0)]]
Texture2D textureColor : register(t1, space0);
Texture2D textures[] : register(t1, space0);
[[vk::binding(1, 0)]]
SamplerState samplerColor : register(s1, space0);
SamplerState samplers[] : register(s1, space0);
struct VSOutput
{
float4 Pos : SV_POSITION;
[[vk::location(0)]] float2 UV : TEXCOORD0;
[[vk::location(1)]] nointerpolation int TexIndex : TEXCOORD1;
};
float4 main(VSOutput input) : SV_TARGET0
float4 main(VSOutput input) : SV_TARGET
{
return textureColor.Sample(samplerColor, input.UV);
return textures[NonUniformResourceIndex(input.TexIndex)].Sample(samplers[NonUniformResourceIndex(input.TexIndex)], input.UV);
}
@@ -15,18 +15,18 @@
* limitations under the License.
*/
struct VSInput
{
[[vk::location(0)]] float3 Pos : POSITION0;
[[vk::location(1)]] float2 UV : TEXCOORD0;
[[vk::location(2)]] float3 Normal : NORMAL0;
[[vk::location(2)]] int TexIndex : TEXCOORD1;
};
struct UBO
{
float4x4 projection;
float4x4 model;
float4 viewPos;
float4x4 view;
};
[[vk::binding(0, 0)]]
ConstantBuffer<UBO> ubo : register(b0, space0);
@@ -35,12 +35,14 @@ struct VSOutput
{
float4 Pos : SV_POSITION;
[[vk::location(0)]] float2 UV : TEXCOORD0;
[[vk::location(1)]] nointerpolation int TexIndex : TEXCOORD1;
};
VSOutput main(VSInput input)
{
VSOutput output = (VSOutput) 0;
output.UV = input.UV;
output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
output.TexIndex = input.TexIndex;
output.Pos = mul(ubo.projection, mul(ubo.view, float4(input.Pos.xyz, 1.0)));
return output;
};
}
@@ -1,4 +1,4 @@
/* Copyright (c) 2024, Sascha Willems
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: Apache-2.0
*
@@ -15,16 +15,18 @@
* limitations under the License.
*/
Texture2D textureColor : register(t1);
SamplerState samplerColor : register(s1);
[[vk::binding(1, 0)]]
Sampler2D textures[] : register(t1, space0);
struct VSOutput
{
float4 Pos : SV_POSITION;
[[vk::location(0)]] float2 UV : TEXCOORD0;
[[vk::location(1)]] nointerpolation int TexIndex : TEXCOORD1;
};
[shader("fragment")]
float4 main(VSOutput input) : SV_TARGET
{
return textureColor.Sample(samplerColor, input.UV);
return textures[NonUniformResourceIndex(input.TexIndex)].Sample(input.UV);
}
@@ -1,4 +1,4 @@
/* Copyright (c) 2024, Sascha Willems
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: Apache-2.0
*
@@ -15,34 +15,35 @@
* limitations under the License.
*/
struct VSInput
{
[[vk::location(0)]] float3 Pos : POSITION0;
[[vk::location(1)]] float2 UV : TEXCOORD0;
[[vk::location(2)]] int TexIndex : TEXCOORD1;
};
struct UBO
{
float4x4 projection;
float4x4 view;
float4x4 model;
};
[[vk::binding(0, 0)]]
ConstantBuffer<UBO> ubo : register(b0);
ConstantBuffer<UBO> ubo : register(b0, space0);
struct VSOutput
{
float4 Pos : SV_POSITION;
[[vk::location(0)]] float2 UV : TEXCOORD0;
[[vk::location(1)]] nointerpolation uint ColorOffset : TEXCOORD1;
[[vk::location(1)]] nointerpolation int TexIndex : TEXCOORD1;
};
[shader("vertex")]
VSOutput main(VSInput input)
{
VSOutput output = (VSOutput) 0;
output.UV = input.UV;
output.ColorOffset = input.Pos.z == 1.0 ? 4 : 0;
output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, float4(input.Pos.xyz, 1.0))));
output.TexIndex = input.TexIndex;
output.Pos = mul(ubo.projection, mul(ubo.view, float4(input.Pos.xyz, 1.0)));
return output;
}
@@ -0,0 +1,15 @@
#version 450
// 输入变量(与顶点着色器输出对应)
layout(location = 0) in vec2 inTexCoord;
// 输出颜色
layout(location = 0) out vec4 outColor;
// 纹理采样器
layout(binding = 0) uniform sampler2D texSampler;
void main() {
// 采样纹理
outColor = texture(texSampler, inTexCoord);
}
@@ -0,0 +1,58 @@
#version 450
// 硬编码的顶点数据 - 4个顶点组成三角形带覆盖整个屏幕
// const vec2 positions[6] = vec2[6](
// vec2( 1.0, 1.0), // 右上 - 三角形1
// vec2(-1.0, 1.0), // 左上 - 三角形1
// vec2(-1.0, -1.0), // 左下 - 三角形1
// vec2(-1.0, -1.0), // 左下 - 三角形2
// vec2( 1.0, -1.0), // 右下 - 三角形2
// vec2( 1.0, 1.0) // 右上 - 三角形2
// );
const float offset = (640-480)/(480.0);
const vec2 positions[6] = vec2[6](
vec2( 1.0, -1.0 -offset), // 右下
vec2(1.0, 1.0 + offset), // 右上 - 三角形1
vec2(-1.0, 1.0 + offset), // 左上 - 三角形1
vec2(-1.0, 1.0 + offset), // 左上 - 三角形2
vec2( -1.0, -1.0-offset), // 左下 - 三角形2
vec2( 1.0, -1.0-offset) // 右下 - 三角形2
);
const vec2 texCoords[6] = vec2[6](
vec2(1.0, 1.0), // 右上
vec2(0.0, 1.0), // 左上
vec2(0.0, 0.0), // 左下
vec2(0.0, 0.0), // 左下
vec2(1.0, 0.0), // 右下
vec2(1.0, 1.0) // 右上
);
// 定义 Push Constant,只有一个 float
layout(push_constant) uniform PushConstants {
float myValue;
} pushConstants;
// 输出变量
layout(location = 0) out vec2 outTexCoord;
void main() {
// 获取顶点位置
vec2 position = positions[gl_VertexIndex];
position = position * pushConstants.myValue;
// 设置输出位置(Vulkan使用不同的坐标系)
gl_Position = vec4(position, 0.0, 1.0);
// 输出纹理坐标
outTexCoord = texCoords[gl_VertexIndex];
}
@@ -0,0 +1,43 @@
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 1) in vec3 fragLineStart;
layout(location = 2) in vec3 fragLineEnd;
layout(location = 0) out vec4 outColor;
layout(binding = 0) uniform UniformBufferObject {
mat4 proj;
mat4 model;
mat4 view;
} ubo;
layout(binding = 1) uniform DashParameters {
float dashSize;
float gapSize;
float dashOffset;
float uAASize;
int useWorldSpace;
} dashParams;
void main() {
vec2 fragPos = gl_FragCoord.xy;
// 网格参数
float gridSize = 14.0; // 网格大小
float dotRadius = 6.0; // 点状半径
// 计算当前片段所在的网格和网格中心坐标
vec2 gridCoord = fragPos / gridSize;
vec2 gridCenter = (floor(gridCoord) + 0.5) * gridSize;
// 计算片段到网格中心的距离
float distanceToCenter = length(fragPos - gridCenter);
// 如果距离超过点状半径,丢弃(创建点状虚线)
if (distanceToCenter > dotRadius && fragColor.y > 0.5) {
discard;
}
outColor = vec4(fragColor, 1.0);
}
@@ -0,0 +1,36 @@
#version 450
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec3 inLineStart;
layout(location = 3) in vec3 inLineEnd;
layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec3 fragLineStart;
layout(location = 2) out vec3 fragLineEnd;
layout(binding = 0) uniform UniformBufferObject {
mat4 proj;
mat4 model;
mat4 view;
} ubo;
layout(push_constant) uniform PushConstants {
float myValue;
} pushConstants;
void main() {
mat4 worldviewproj = ubo.proj * ubo.view * ubo.model;
//gl_Position = worldviewproj * vec4(inPosition, 1.0);
vec2 position = vec2(inPosition.xy*2 -1);
//gl_Position = vec4(inPosition.xy*2 -1, 0.5, 1.0);
position = position * pushConstants.myValue;
gl_Position = vec4(position, 0.5, 1.0);
fragColor = inColor;
fragLineStart = (worldviewproj * vec4(inLineStart, 1.0)).xyz;
fragLineEnd = (worldviewproj * vec4(inLineEnd, 1.0)).xyz;
}
@@ -0,0 +1,11 @@
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(1, 0, 0, 1.0);
}
@@ -0,0 +1,23 @@
#version 450
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec3 inLineStart;
layout(location = 3) in vec3 inLineEnd;
layout(location = 0) out vec3 fragColor;
layout(binding = 0) uniform UniformBufferObject {
mat4 proj;
mat4 model;
mat4 view;
} ubo;
void main() {
//mat4 worldviewproj = ubo.proj * ubo.view * ubo.model;
mat4 mvp = ubo.proj * ubo.view * ubo.model;
//gl_Position = worldviewproj * vec4(inPosition, 1.0);
gl_Position = mvp * vec4(inPosition, 1.0);
fragColor = inColor;
}
@@ -0,0 +1,11 @@
#version 450
// 从顶点着色器接收的颜色
layout(location = 0) in vec3 fragColor;
// 输出颜色
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0); // 使用传入的颜色
}
@@ -0,0 +1,31 @@
#version 450
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 0) out vec3 fragColor;
layout(binding = 0) uniform UniformBufferObject {
mat4 proj;
mat4 model;
mat4 view;
} ubo;
layout(push_constant) uniform PushConstants {
float myValue;
} pushConstants;
void main() {
// 预计算MVP矩阵以减少乘法次数
//mat4 mvp = ubo.proj * ubo.view * ubo.model;
//vec3 pos = vec3(2.0, inPosition.y, inPosition.z);
//gl_Position = mvp * vec4(inPosition, 1.0);
vec2 position = vec2(inPosition.xy*2 -1);
//gl_Position = vec4(inPosition.xy*2 -1, 0.5, 1.0);
position = position * pushConstants.myValue;
gl_Position = vec4(position, 0.5, 1.0);
fragColor = inColor;
gl_PointSize = 2.0;
}
@@ -0,0 +1,26 @@
#version 450
layout (binding = 1) uniform sampler2D samplerColor;
layout (location = 0) in vec2 inUV;
layout (location = 1) in vec3 inNormal;
layout (location = 0) out vec4 outFragColor;
void main()
{
vec4 color = texture(samplerColor, inUV);
vec3 N = normalize(inNormal);
vec4 textureColor = color;
if (textureColor.a < 0.1) {
//textureColor = vec4(0.2, 0.2, 0.2, 0.5);
discard;
}
outFragColor = textureColor;
// outFragColor = vec4(1, 0, 0, 1);
}
@@ -0,0 +1,39 @@
#version 450
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec2 inUV;
layout (location = 2) in vec3 inNormal;
layout (binding = 0) uniform UBO
{
mat4 projection;
mat4 model;
vec4 viewPos;
float lodBias;
} ubo;
layout (location = 0) out vec2 outUV;
layout (location = 1) out vec3 outNormal;
layout(push_constant) uniform PushConstants {
float myValue;
} pushConstants;
out gl_PerVertex
{
vec4 gl_Position;
};
void main()
{
outUV = inUV;
vec2 position = vec2(inPos.xy*2 -1);
position = position * pushConstants.myValue;
gl_Position = vec4(position, 0.5, 1.0);
vec4 pos = ubo.model * vec4(inPos, 1.0);
outNormal = mat3(inverse(transpose(ubo.model))) * inNormal;
}
@@ -15,23 +15,29 @@
* limitations under the License.
*/
[[vk::binding(1, 0)]]
Texture2D textureColor : register(t1);
[[vk::binding(1, 0)]]
SamplerState samplerColor : register(s1);
struct VSOutput
{
float4 Pos : SV_POSITION;
[[vk::location(0)]] float3 Normal : NORMAL0;
[[vk::location(1)]] float3 Color : COLOR0;
[[vk::location(2)]] float3 ViewVec : TEXCOORD0;
[[vk::location(3)]] float3 LightVec : TEXCOORD1;
[[vk::location(0)]] float2 UV : TEXCOORD0;
[[vk::location(1)]] float LodBias : FLOAT0;
[[vk::location(2)]] float3 Normal : NORMAL0;
[[vk::location(3)]] float3 ViewVec : VECTOR0;
[[vk::location(4)]] float3 LightVec : VECTOR1;
};
float4 main(VSOutput input) : SV_TARGET0
{
float4 color = textureColor.SampleBias(samplerColor, input.UV, input.LodBias);
float3 N = normalize(input.Normal);
float3 L = normalize(input.LightVec);
float3 V = normalize(input.ViewVec);
float3 R = reflect(-L, N);
float3 ambient = (0.25).xxx;
float3 diffuse = max(dot(N, L), 0.0).xxx;
float3 specular = (pow(max(dot(R, V), 0.0), 16.0)).xxx * 0.75;
return float4((ambient + diffuse) * input.Color + specular, 1.0);
float3 specular = (pow(max(dot(R, V), 0.0), 16.0)).xxx * color.a;
return float4(diffuse * color.rgb + specular, 1.0);
}
@@ -27,8 +27,7 @@ struct UBO
float4x4 projection;
float4x4 model;
float4 viewPos;
float locSpeed;
float globSpeed;
float lodBias;
};
[[vk::binding(0, 0)]]
ConstantBuffer<UBO> ubo : register(b0);
@@ -37,12 +36,25 @@ struct VSOutput
{
float4 Pos : SV_POSITION;
[[vk::location(0)]] float2 UV : TEXCOORD0;
[[vk::location(1)]] float LodBias : FLOAT0;
[[vk::location(2)]] float3 Normal : NORMAL0;
[[vk::location(3)]] float3 ViewVec : VECTOR0;
[[vk::location(4)]] float3 LightVec : VECTOR1;
};
VSOutput main(VSInput input)
{
VSOutput output = (VSOutput) 0;
float3 vecPos = mul((float3x3) ubo.model, input.Pos.xyz).xyz;
float3 lightPos = mul((float3x3) ubo.model, (1.0).xxx);
output.UV = input.UV;
output.LodBias = ubo.lodBias;
output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
output.Normal = mul((float3x3) ubo.model, input.Normal);
output.LightVec = lightPos - vecPos;
output.ViewVec = ubo.viewPos.xyz - vecPos;
return output;
}
@@ -1,5 +1,5 @@
#version 450
/* Copyright (c) 2024-2025, Huawei Technologies Co., Ltd.
#version 320 es
/* Copyright (c) 2019, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
@@ -16,6 +16,8 @@
* limitations under the License.
*/
precision mediump float;
layout(location = 0) in vec3 in_color;
layout(location = 0) out vec4 out_color;
@@ -1,5 +1,5 @@
#version 450
/* Copyright (c) 2023-2024, Mobica Limited
#version 320 es
/* Copyright (c) 2019-2023, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
@@ -16,10 +16,12 @@
* limitations under the License.
*/
precision mediump float;
layout(location = 0) out vec3 out_color;
vec2 triangle_positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
@@ -32,6 +34,7 @@ vec3 triangle_colors[3] = vec3[](
void main()
{
gl_Position = vec4(triangle_positions[gl_VertexIndex], 0.0, 1.0);
gl_Position = vec4(triangle_positions[gl_VertexIndex], 0.5, 1.0);
out_color = triangle_colors[gl_VertexIndex];
}
@@ -1,5 +1,5 @@
#version 450
/* Copyright (c) 2020-2024, Sascha Willems
/* Copyright (c) 2019, Sascha Willems
*
* SPDX-License-Identifier: Apache-2.0
*
@@ -16,14 +16,14 @@
* limitations under the License.
*/
layout (binding = 0) uniform sampler2D samplerColor0;
layout (binding = 1) uniform sampler2D samplerColor1;
layout (binding = 0) uniform sampler2D fontSampler;
layout (location = 0) in vec2 inUV;
layout (location = 1) in vec4 inColor;
layout (location = 0) out vec4 outColor;
void main()
{
outColor = texture(samplerColor0, inUV);
outColor = inColor * texture(fontSampler, inUV);
}
@@ -1,5 +1,5 @@
#version 450
/* Copyright (c) 2021-2024, Sascha Willems
/* Copyright (c) 2019, Sascha Willems
*
* SPDX-License-Identifier: Apache-2.0
*
@@ -16,21 +16,25 @@
* limitations under the License.
*/
layout (location = 0) in vec3 inPos;
layout (location = 0) in vec2 inPos;
layout (location = 1) in vec2 inUV;
layout (location = 2) in vec3 inNormal;
layout (location = 2) in vec4 inColor;
layout (set = 0, binding = 0) uniform UBO
{
mat4 projection;
mat4 model;
vec4 viewPos;
} ubo;
layout (push_constant) uniform PushConstants {
mat4 transform;
} pushConstants;
layout (location = 0) out vec2 outUV;
layout (location = 1) out vec4 outColor;
out gl_PerVertex
{
vec4 gl_Position;
};
void main()
{
outUV = inUV;
gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
outColor = inColor;
gl_Position = pushConstants.transform * vec4(inPos.xy, 0.0, 1.0);
}
+1 -18
View File
@@ -1,22 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (c) 2019-2024, Arm Limited and Contributors
-
- SPDX-License-Identifier: Apache-2.0
-
- Licensed under the Apache License, Version 2.0 the "License";
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
@@ -0,0 +1,391 @@
package com.khronos.vulkan_samples;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;
public class AssetsCopyUtil {
private static final String TAG = "AssetsCopyUtil";
private static final String PREF_NAME = "assets_copy_pref";
private static final String PREF_KEY_COPIED_FILES = "copied_files";
/**
* 复制 assets 到应用专属外部存储的 files 目录(递归复制子文件夹)
*/
public static void copyAssetsToAppFiles(Context context, String assetsPath) {
try {
AssetManager assetManager = context.getAssets();
// 获取应用专属外部存储的 files 目录
File externalFilesDir = context.getExternalFilesDir(null);
if (externalFilesDir == null) {
Log.e(TAG, "External files directory is not available");
return;
}
// 递归复制
copyAssetsRecursive(context, assetManager, assetsPath, externalFilesDir.getAbsolutePath());
Log.i(TAG, "Assets copy completed for: " + assetsPath);
} catch (Exception e) {
Log.e(TAG, "Error copying assets from: " + assetsPath, e);
}
}
/**
* 递归复制 assets 中的文件和文件夹
*/
private static void copyAssetsRecursive(Context context, AssetManager assetManager,
String assetsPath, String targetBasePath) {
try {
// 获取 assets 路径下的所有项目
String[] items = assetManager.list(assetsPath);
if (items == null || items.length == 0) {
Log.w(TAG, "No items found in assets path: " + assetsPath);
return;
}
// 创建目标目录
File targetDir = new File(targetBasePath, assetsPath);
if (!targetDir.exists() && !targetDir.mkdirs()) {
Log.e(TAG, "Failed to create target directory: " + targetDir.getAbsolutePath());
return;
}
// 获取已复制的文件列表
Set<String> copiedFiles = getCopiedFiles(context);
boolean hasNewFiles = false;
for (String item : items) {
String assetItemPath = assetsPath.isEmpty() ? item : assetsPath + "/" + item;
String fileKey = assetItemPath;
// 检查是否已经复制过
if (copiedFiles.contains(fileKey)) {
Log.d(TAG, "Item already copied: " + assetItemPath);
continue;
}
try {
// 判断是文件还是目录的正确方法
boolean isFile = isAssetFile(assetManager, assetsPath, item);
if (isFile) {
// 复制文件
if (copySingleFile(assetManager, assetItemPath, targetDir, item)) {
copiedFiles.add(fileKey);
hasNewFiles = true;
Log.d(TAG, "Successfully copied file: " + assetItemPath);
}
} else {
// 递归复制子目录
copyAssetsRecursive(context, assetManager, assetItemPath, targetBasePath);
// 将目录也标记为已处理
copiedFiles.add(fileKey);
hasNewFiles = true;
Log.d(TAG, "Processed directory: " + assetItemPath);
}
} catch (Exception e) {
Log.e(TAG, "Error processing: " + assetItemPath, e);
}
}
// 如果有新文件被复制,更新记录
if (hasNewFiles) {
saveCopiedFiles(context, copiedFiles);
}
} catch (Exception e) {
Log.e(TAG, "Error in recursive copy for path: " + assetsPath, e);
}
}
/**
* 正确判断 assets 中的项目是文件还是目录
*/
private static boolean isAssetFile(AssetManager assetManager, String basePath, String item) {
try {
// 方法1:尝试列出子项,如果能列出且不为空,说明是目录
String testPath = basePath.isEmpty() ? item : basePath + "/" + item;
String[] subItems = assetManager.list(testPath);
// 如果子项为空,说明是文件或者是空目录
if (subItems == null) {
return true; // 应该是文件
}
// 如果有子项,说明是目录
if (subItems.length > 0) {
return false; // 是目录
}
// 如果子项为空数组,需要进一步判断
// 尝试打开它,如果能打开说明是文件,不能打开说明是空目录
try {
InputStream test = assetManager.open(testPath);
test.close();
return true; // 能打开,是文件
} catch (Exception e) {
return false; // 不能打开,是空目录
}
} catch (Exception e) {
Log.e(TAG, "Error checking asset type: " + item, e);
return true; // 默认当作文件处理
}
}
/**
* 更可靠的递归复制方法
*/
public static void copyAssetsToAppFilesSimple(Context context, String assetsPath) {
try {
AssetManager assetManager = context.getAssets();
File externalFilesDir = context.getExternalFilesDir(null);
if (externalFilesDir == null) {
Log.e(TAG, "External storage not available");
return;
}
// 创建目标目录
File targetBaseDir = new File(externalFilesDir, assetsPath);
if (!targetBaseDir.exists() && !targetBaseDir.mkdirs()) {
Log.e(TAG, "Failed to create base directory");
return;
}
copyAssetsRecursiveSimple(assetManager, assetsPath, targetBaseDir.getAbsolutePath());
Log.i(TAG, "Simple copy completed for: " + assetsPath);
} catch (Exception e) {
Log.e(TAG, "Error in simple copy", e);
}
}
private static void copyAssetsRecursiveSimple(AssetManager assetManager, String assetsPath, String targetPath) {
try {
String[] items = assetManager.list(assetsPath);
if (items == null || items.length == 0) {
return;
}
for (String item : items) {
String fullAssetPath = assetsPath.isEmpty() ? item : assetsPath + "/" + item;
// 判断是文件还是目录
boolean isFile = isAssetFileSimple(assetManager, fullAssetPath);
if (isFile) {
// 复制文件
copySingleFileSimple(assetManager, fullAssetPath, targetPath, item);
Log.d(TAG, "Copied file: " + fullAssetPath + " to " + targetPath);
} else {
// 创建子目录并递归复制
File subDir = new File(targetPath, item);
if (!subDir.exists()) {
subDir.mkdirs();
}
copyAssetsRecursiveSimple(assetManager, fullAssetPath, subDir.getAbsolutePath());
Log.d(TAG, "Processed directory: " + fullAssetPath);
}
}
} catch (Exception e) {
Log.e(TAG, "Error in simple recursive copy: " + assetsPath, e);
}
}
private static boolean isAssetFileSimple(AssetManager assetManager, String path) {
try {
String[] subItems = assetManager.list(path);
if (subItems == null) return true; // 应该是文件
if (subItems.length > 0) return false; // 有子项,是目录
// 空数组,尝试打开判断
try {
InputStream test = assetManager.open(path);
test.close();
return true; // 能打开,是文件
} catch (Exception e) {
return false; // 不能打开,是空目录
}
} catch (Exception e) {
return true; // 出错时默认当作文件
}
}
/**
* 复制单个文件
*/
private static boolean copySingleFile(AssetManager assetManager, String assetFilePath, File targetDir, String filename) {
InputStream in = null;
OutputStream out = null;
try {
File outFile = new File(targetDir, filename);
in = assetManager.open(assetFilePath);
out = new FileOutputStream(outFile);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
Log.d(TAG, "File copied: " + outFile.getAbsolutePath());
return true;
} catch (Exception e) {
Log.e(TAG, "Failed to copy file: " + assetFilePath, e);
return false;
} finally {
try {
if (in != null) in.close();
if (out != null) {
out.flush();
out.close();
}
} catch (Exception e) {
Log.e(TAG, "Error closing streams", e);
}
}
}
private static void copySingleFileSimple(AssetManager assetManager, String assetFilePath,
String targetPath, String filename) {
InputStream in = null;
OutputStream out = null;
try {
File outFile = new File(targetPath, filename);
in = assetManager.open(assetFilePath);
out = new FileOutputStream(outFile);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
} catch (Exception e) {
Log.e(TAG, "Error copying: " + assetFilePath, e);
} finally {
try {
if (in != null) in.close();
if (out != null) {
out.flush();
out.close();
}
} catch (Exception e) {
Log.e(TAG, "Error closing streams", e);
}
}
}
/**
* 调试方法:打印 assets 结构
*/
public static void printAssetsStructure(Context context, String path) {
try {
Log.d(TAG, "=== Assets Structure for: " + path + " ===");
AssetManager am = context.getAssets();
printAssetsRecursive(am, path, 0);
Log.d(TAG, "=== End of Assets Structure ===");
} catch (Exception e) {
Log.e(TAG, "Error printing assets structure", e);
}
}
private static void printAssetsRecursive(AssetManager am, String path, int depth) {
try {
String indent = "";
for (int i = 0; i < depth; i++) indent += " ";
String[] items = am.list(path);
if (items != null) {
for (String item : items) {
String fullPath = path.isEmpty() ? item : path + "/" + item;
boolean isFile = isAssetFileSimple(am, fullPath);
if (isFile) {
Log.d(TAG, indent + "📄 " + item + " (file)");
} else {
Log.d(TAG, indent + "📁 " + item + " (dir)");
printAssetsRecursive(am, fullPath, depth + 1);
}
}
}
} catch (Exception e) {
Log.e(TAG, "Error in recursive print", e);
}
}
/**
* 获取已复制的文件列表
*/
@SuppressWarnings("unchecked")
private static Set<String> getCopiedFiles(Context context) {
return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.getStringSet(PREF_KEY_COPIED_FILES, new HashSet<String>());
}
/**
* 保存已复制的文件列表
*/
private static void saveCopiedFiles(Context context, Set<String> copiedFiles) {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit()
.putStringSet(PREF_KEY_COPIED_FILES, new HashSet<>(copiedFiles))
.apply();
}
/**
* 清空复制记录
*/
public static void clearCopyRecords(Context context) {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit()
.remove(PREF_KEY_COPIED_FILES)
.apply();
Log.d(TAG, "Copy records cleared");
}
/**
* 检查外部存储是否可用
*/
public static boolean isExternalStorageAvailable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
/**
* 获取目标文件的完整路径
*/
public static String getFilePath(Context context, String relativePath) {
File externalFilesDir = context.getExternalFilesDir(null);
if (externalFilesDir == null) return null;
File targetFile = new File(externalFilesDir, relativePath);
return targetFile.getAbsolutePath();
}
/**
* 检查文件是否存在
*/
public static boolean fileExists(Context context, String relativePath) {
File externalFilesDir = context.getExternalFilesDir(null);
if (externalFilesDir == null) return false;
File targetFile = new File(externalFilesDir, relativePath);
return targetFile.exists();
}
}
@@ -18,20 +18,24 @@ public class CameraDataFetcher {
// 数据模型类
public static class CameraData {
public float fov;
public float x;
public float y;
public float fov = 47;
public float x = -0.5f;
public float y = -0.51f;
public float z;
public float sx;
public float sy;
public float sz;
public float sx = 1;
public float sy = 1;
public float sz = 1;
public float rotx;
public float roty;
public float rotz;
public float camx;
public float camy;
public float camz;
public float z_rate;
public float z_rate = -1;
public float dashSize = 5;
public float gapSize = 3;
public float dashOffset = 0;
public float uAASize = 1.5f;
// 从JSONObject解析数据
public static CameraData fromJson(JSONObject json) {
@@ -51,6 +55,10 @@ public class CameraDataFetcher {
data.camy = (float)json.getDouble("camy");
data.camz = (float)json.getDouble("camz");
data.z_rate = (float)json.getDouble("z_rate");
data.dashSize = (float)json.getDouble("dashSize");
data.gapSize = (float)json.getDouble("gapSize");
data.dashOffset = (float)json.getDouble("dashOffset");
data.uAASize = (float)json.getDouble("uAASize");
} catch (Exception e) {
Log.e("CameraData", "解析JSON失败: " + e.getMessage());
}
@@ -17,6 +17,7 @@ package com.khronos.vulkan_samples
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.media.MediaMetadataRetriever
import android.net.Uri
@@ -103,7 +104,8 @@ class FaceLandmarkerHelper(
.setMinTrackingConfidence(minFaceTrackingConfidence)
.setMinFacePresenceConfidence(minFacePresenceConfidence)
.setNumFaces(maxNumFaces)
.setOutputFaceBlendshapes(true)
.setOutputFaceBlendshapes(false)
.setOutputFacialTransformationMatrixes(false)
.setRunningMode(runningMode)
// The ResultListener and ErrorListener only use for LIVE_STREAM mode.
@@ -138,6 +140,51 @@ class FaceLandmarkerHelper(
}
}
private var frameId: Long = 0
// 简化的延时跟踪
private val frameTimings = mutableMapOf<Long, FrameTiming>()
private var lastFpsUpdateTime: Long = 0
private var frameCount: Int = 0
private var currentFps: Double = 0.0
// 内部数据类用于存储时间信息
private data class FrameTiming(
var captureTime: Long = 0,
var preprocessStartTime: Long = 0,
var preprocessEndTime: Long = 0,
var detectionStartTime: Long = 0
)
// 辅助方法:记录帧时间信息
private fun recordFrameTiming(frameId: Long, block: (FrameTiming) -> Unit) {
val timing = frameTimings.getOrPut(frameId) { FrameTiming() }
block(timing)
}
// 辅助方法:更新FPS计算
private fun updateFps() {
frameCount++
val currentTime = SystemClock.uptimeMillis()
if (lastFpsUpdateTime == 0L) {
lastFpsUpdateTime = currentTime
return
}
val timeDiff = currentTime - lastFpsUpdateTime
if (timeDiff >= 1000) { // 每秒更新一次FPS
currentFps = frameCount * 1000.0 / timeDiff
frameCount = 0
lastFpsUpdateTime = currentTime
}
}
// 辅助方法:清理旧的时间记录
private fun cleanupOldTimings(currentFrameId: Long) {
val framesToRemove = frameTimings.keys.filter { it < currentFrameId - 10 }
framesToRemove.forEach { frameTimings.remove(it) }
}
// Convert the ImageProxy to MP Image and feed it to FacelandmakerHelper.
fun detectLiveStream(
imageProxy: ImageProxy,
@@ -151,6 +198,15 @@ class FaceLandmarkerHelper(
}
val frameTime = SystemClock.uptimeMillis()
val currentFrameId = frameId++
val captureTime = SystemClock.uptimeMillis()
// 记录捕获时间
recordFrameTiming(currentFrameId) {
it.captureTime = captureTime
it.preprocessStartTime = SystemClock.uptimeMillis()
}
// Copy out RGB bits from the frame to a bitmap buffer
val bitmapBuffer =
Bitmap.createBitmap(
@@ -180,6 +236,9 @@ class FaceLandmarkerHelper(
matrix, true
)
//val bitmap = BitmapFactory.decodeFile("/sdcard/Android/data/com.khronos.vulkan_samples/files/assets/face480.png")
//val mpImage = BitmapImageBuilder(bitmap).build()
// Convert the input Bitmap object to an MPImage object to run inference
val mpImage = BitmapImageBuilder(rotatedBitmap).build()
@@ -194,152 +253,57 @@ class FaceLandmarkerHelper(
// be returned in returnLivestreamResult function
}
// Accepts the URI for a video file loaded from the user's gallery and attempts to run
// face landmarker inference on the video. This process will evaluate every
// frame in the video and attach the results to a bundle that will be
// returned.
fun detectVideoFile(
videoUri: Uri,
inferenceIntervalMs: Long
): VideoResultBundle? {
if (runningMode != RunningMode.VIDEO) {
throw IllegalArgumentException(
"Attempting to call detectVideoFile" +
" while not using RunningMode.VIDEO"
)
}
// Inference time is the difference between the system time at the start and finish of the
// process
val startTime = SystemClock.uptimeMillis()
var didErrorOccurred = false
// Load frames from the video and run the face landmarker.
val retriever = MediaMetadataRetriever()
retriever.setDataSource(context, videoUri)
val videoLengthMs =
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
?.toLong()
// Note: We need to read width/height from frame instead of getting the width/height
// of the video directly because MediaRetriever returns frames that are smaller than the
// actual dimension of the video file.
val firstFrame = retriever.getFrameAtTime(0)
val width = firstFrame?.width
val height = firstFrame?.height
// If the video is invalid, returns a null detection result
if ((videoLengthMs == null) || (width == null) || (height == null)) return null
// Next, we'll get one frame every frameInterval ms, then run detection on these frames.
val resultList = mutableListOf<FaceLandmarkerResult>()
val numberOfFrameToRead = videoLengthMs.div(inferenceIntervalMs)
for (i in 0..numberOfFrameToRead) {
val timestampMs = i * inferenceIntervalMs // ms
retriever
.getFrameAtTime(
timestampMs * 1000, // convert from ms to micro-s
MediaMetadataRetriever.OPTION_CLOSEST
)
?.let { frame ->
// Convert the video frame to ARGB_8888 which is required by the MediaPipe
val argb8888Frame =
if (frame.config == Bitmap.Config.ARGB_8888) frame
else frame.copy(Bitmap.Config.ARGB_8888, false)
// Convert the input Bitmap object to an MPImage object to run inference
val mpImage = BitmapImageBuilder(argb8888Frame).build()
// Run face landmarker using MediaPipe Face Landmarker API
faceLandmarker?.detectForVideo(mpImage, timestampMs)
?.let { detectionResult ->
resultList.add(detectionResult)
} ?: {
didErrorOccurred = true
faceLandmarkerHelperListener?.onError(
"ResultBundle could not be returned" +
" in detectVideoFile"
)
}
}
?: run {
didErrorOccurred = true
faceLandmarkerHelperListener?.onError(
"Frame at specified time could not be" +
" retrieved when detecting in video."
)
}
}
retriever.release()
val inferenceTimePerFrameMs =
(SystemClock.uptimeMillis() - startTime).div(numberOfFrameToRead)
return if (didErrorOccurred) {
null
} else {
VideoResultBundle(resultList, inferenceTimePerFrameMs, height, width)
}
}
// Accepted a Bitmap and runs face landmarker inference on it to return
// results back to the caller
fun detectImage(image: Bitmap): ResultBundle? {
if (runningMode != RunningMode.IMAGE) {
throw IllegalArgumentException(
"Attempting to call detectImage" +
" while not using RunningMode.IMAGE"
)
}
// Inference time is the difference between the system time at the
// start and finish of the process
val startTime = SystemClock.uptimeMillis()
// Convert the input Bitmap object to an MPImage object to run inference
val mpImage = BitmapImageBuilder(image).build()
// Run face landmarker using MediaPipe Face Landmarker API
faceLandmarker?.detect(mpImage)?.also { landmarkResult ->
val inferenceTimeMs = SystemClock.uptimeMillis() - startTime
return ResultBundle(
landmarkResult,
inferenceTimeMs,
image.height,
image.width
)
}
// If faceLandmarker?.detect() returns null, this is likely an error. Returning null
// to indicate this.
faceLandmarkerHelperListener?.onError(
"Face Landmarker failed to detect."
)
return null
}
// Return the landmark result to this FaceLandmarkerHelper's caller
private fun returnLivestreamResult(
result: FaceLandmarkerResult,
input: MPImage
) {
val resultTime = SystemClock.uptimeMillis()
val frameId = result.timestampMs()
if( result.faceLandmarks().size > 0 ) {
val finishTimeMs = SystemClock.uptimeMillis()
val inferenceTime = finishTimeMs - result.timestampMs()
// 计算各种延时
val timings = frameTimings[frameId]
if (timings != null) {
val totalLatency = resultTime - timings.captureTime
val preprocessLatency = timings.preprocessEndTime - timings.preprocessStartTime
val detectionLatency = resultTime - timings.detectionStartTime
val captureToDetectionLatency = timings.detectionStartTime - timings.captureTime
faceLandmarkerHelperListener?.onResults(
ResultBundle(
result,
inferenceTime,
input.height,
input.width
Log.i("TimeLatency",
"总延时: ${totalLatency}ms | " +
"预处理: ${preprocessLatency}ms | " +
"检测: ${detectionLatency}ms | " +
"捕获到检测: ${captureToDetectionLatency}ms | " +
"FPS: ${String.format("%.1f", currentFps)}"
)
)
val finishTimeMs = SystemClock.uptimeMillis()
val inferenceTime = finishTimeMs - result.timestampMs()
faceLandmarkerHelperListener?.onResults(
ResultBundle(
result,
inferenceTime, // 主要是检测时间
input.height,
input.width,
)
)
} else {
val finishTimeMs = SystemClock.uptimeMillis()
val inferenceTime = finishTimeMs - result.timestampMs()
Log.i("TimeLatency",
"总延时: ${inferenceTime}ms | "
)
faceLandmarkerHelperListener?.onResults(
ResultBundle(
result,
inferenceTime,
input.height,
input.width
)
)
}
}
else {
faceLandmarkerHelperListener?.onEmpty()
@@ -375,13 +339,6 @@ class FaceLandmarkerHelper(
val inputImageWidth: Int,
)
data class VideoResultBundle(
val results: List<FaceLandmarkerResult>,
val inferenceTime: Long,
val inputImageHeight: Int,
val inputImageWidth: Int,
)
interface LandmarkerListener {
fun onError(error: String, errorCode: Int = OTHER_ERROR)
fun onResults(resultBundle: ResultBundle)
@@ -6,6 +6,7 @@ import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.WindowManager;
import android.widget.Toast;
@@ -23,6 +24,7 @@ import com.google.androidgamesdk.GameActivity;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.mediapipe.tasks.components.containers.NormalizedLandmark;
import com.google.mediapipe.tasks.vision.core.RunningMode;
import com.google.mediapipe.tasks.vision.facelandmarker.FaceLandmarker;
import com.google.mediapipe.tasks.vision.facelandmarker.FaceLandmarkerResult;
import org.jetbrains.annotations.NotNull;
@@ -30,10 +32,12 @@ import org.jetbrains.annotations.NotNull;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MainActivity extends GameActivity implements FaceLandmarkerHelper.LandmarkerListener, CameraDataFetcher.CameraDataCallback {
private ProcessCameraProvider cameraProvider;
@@ -58,6 +62,16 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
@Override
protected void onCreate(Bundle savedInstanceState) {
// if (!AssetsCopyUtil.isExternalStorageAvailable()) {
// Log.w(TAG, "External storage not available, skipping assets copy");
// return;
// }
//AssetsCopyUtil.copyAssetsToAppFiles(this, "assets");
//AssetsCopyUtil.copyAssetsToAppFiles(this, "shaders");
String native_lib_name = getResources().getString(R.string.native_lib_name);
try {
System.loadLibrary(native_lib_name);
@@ -93,16 +107,16 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
});
// 初始化Runnable
cameraDataRunnable = new Runnable() {
@Override
public void run() {
fetchCameraData();
handler.postDelayed(this, INTERVAL); // 再次调度
}
};
// cameraDataRunnable = new Runnable() {
// @Override
// public void run() {
// fetchCameraData();
// handler.postDelayed(this, INTERVAL); // 再次调度
// }
// };
// 开始周期性调用
startPeriodicFetching();
// startPeriodicFetching();
Log.i("MainActivity", "onCreate: ");
@@ -166,13 +180,45 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
imageAnalyzer.setAnalyzer(backgroundExecutor, new ImageAnalysis.Analyzer() {
private long lastCaptureTime = 0;
private int frameCount = 0;
@Override
public void analyze(@NonNull ImageProxy image) {
frameCount++;
long currentTime = System.currentTimeMillis();
try {
//Log.d("Camera", "Received image: " + image.getWidth() + "x" + image.getHeight());
long captureTimeNs = image.getImageInfo().getTimestamp();
long analyzeTime = System.currentTimeMillis();
// 简单直接的计算方法
long captureTimeMs = TimeUnit.NANOSECONDS.toMillis(captureTimeNs);
long latency = analyzeTime - captureTimeMs;
// 帧率计算
if (lastCaptureTime > 0) {
long frameInterval = captureTimeMs - lastCaptureTime;
double fps = 1000.0 / frameInterval;
if (frameCount % 10 == 0) { // 每30帧打印一次
Log.i("TimeLatency", String.format(
"Frame %d - Latency: %d ms, FPS: %.1f",
frameCount, latency, fps
));
}
}
lastCaptureTime = captureTimeMs;
processImageForVulkan(image);
detectFace(image);
if(frameCount %2 == 0)
{
detectFace(image);
}
else
{
image.close();
}
} finally {
//image.close(); // 确保在这里关闭
//Log.d("Camera", "Image closed, ready for next frame");
@@ -254,6 +300,41 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
float[] points = new float[480 * 3];
public class Vector {
public float x, y, z;
public Vector(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
}
}
public static String toObjString(ArrayList<Vector> vertices, ArrayList<Integer> triangleIndices) {
StringBuilder sb = new StringBuilder();
// 写入顶点数据 (v x y z)
for (Vector v : vertices) {
sb.append(String.format("v %.6f %.6f %.6f\n", v.x, v.y, v.z));
}
// 写入面数据 (f i1 i2 i3),OBJ索引从1开始,因此每个索引+1
int size = triangleIndices.size();
for (int i = 0; i < size; i += 3) {
if (i + 2 < size) {
int i1 = triangleIndices.get(i) + 1;
int i2 = triangleIndices.get(i + 1) + 1;
int i3 = triangleIndices.get(i + 2) + 1;
sb.append(String.format("f %d %d %d\n", i1, i2, i3));
} else {
// 如果剩余索引不足3个,说明数据不完整
System.err.println("Warning: Incomplete triangle face at the end of index list.");
}
}
return sb.toString();
}
@Override
public void onResults(FaceLandmarkerHelper.@NotNull ResultBundle resultBundle) {
FaceLandmarkerResult faceLandmarkerResult = resultBundle.getResult();
@@ -268,6 +349,19 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
nativeBuffer.order(ByteOrder.nativeOrder());
}
long start_time = System.currentTimeMillis();
// ArrayList<Vector> vertexs = new ArrayList<>();
// ArrayList<Integer> triangle_index = new ArrayList<>();
//
// for (var conn : FaceLandmarker.FACE_LANDMARKS_TESSELATION) {
// triangle_index.add(conn.start());
// }
//StringBuilder sb = new StringBuilder();
//sb.append("[");
int index = 0;
// Iterate through each detected face
for (List<NormalizedLandmark> faceLandmarks : faceLandmarkerResult.faceLandmarks()) {
@@ -276,32 +370,47 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
int arrayIndex = 0;
for (NormalizedLandmark p : faceLandmarks)
{
//Log.i(TAG, "Index" + index + " x:"+p.x() + " y:"+p.y() + " z:"+p.z());
//sb.append("[");
points[arrayIndex++] = p.x();
//sb.append(""+p.x()+",");
points[arrayIndex++] = p.y();
//sb.append(""+p.y()+",");
points[arrayIndex++] = p.z() * z_rate;
//sb.append(""+p.z());
//vertexs.add(new Vector(p.x(), p.y(), p.z()));
//sb.append("],");
index++;
}
}
//sb.append("]");
//String objStr = sb.toString();
//String objStr = toObjString(vertexs, triangle_index);
// 转换为FloatBuffer并填充数据
FloatBuffer floatBuffer = nativeBuffer.asFloatBuffer();
floatBuffer.put(points);
floatBuffer.position(0);
long cur_time = System.currentTimeMillis();
Log.i("TimeLatency","Result Data ProcessTime:" + (cur_time-start_time));
passDataToNative(nativeBuffer, index, width, height);
Log.i("TimeLatency","passDataToNative ProcessTime:" + (System.currentTimeMillis() - cur_time));
}
public float z_rate = 1.0f;
public float z_rate = -1.0f;
public native void passDataToNative(ByteBuffer buffer, int pointCount, int width, int height);
public native void upDateCameraData(float fov, float x, float y, float z, float sx, float sy, float sz, float rotx, float roty, float rotz, float camx, float camy, float camz);
public native void upDateCameraData(float fov, float x, float y, float z, float sx, float sy, float sz, float rotx, float roty, float rotz,
float camx, float camy, float camz, float dashSize, float gapSize, float dashOffset, float uAASize);
@Override
public void onSuccess(CameraDataFetcher.CameraData data) {
z_rate = data.z_rate;
upDateCameraData(data.fov, data.x, data.y, data.z, data.sx, data.sy, data.sz,
data.rotx, data.roty, data.rotz,
data.camx, data.camy, data.camz
data.camx, data.camy, data.camz,
data.dashSize, data.gapSize, data.dashOffset, data.uAASize
);
canFetch = true;
Log.e(TAG, "GetCameraData " + data.toString());
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 157 KiB

+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 底层:CameraX 预览 -->
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- 上层:GameActivity 的容器 -->
<FrameLayout
android:id="@+id/gameActivityContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 30 KiB

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