feat(render-demo): обновить поддержку OpenGL с добавлением выбора между GLES2 и Core 3.3
This commit is contained in:
@@ -13,13 +13,18 @@ msh-core = { path = "../msh-core" }
|
|||||||
nres = { path = "../nres" }
|
nres = { path = "../nres" }
|
||||||
render-core = { path = "../render-core" }
|
render-core = { path = "../render-core" }
|
||||||
texm = { path = "../texm" }
|
texm = { path = "../texm" }
|
||||||
sdl2 = { version = "0.37", optional = true, default-features = false, features = ["bundled", "static-link"] }
|
|
||||||
glow = { version = "0.16", optional = true }
|
glow = { version = "0.16", optional = true }
|
||||||
image = { version = "0.25", optional = true, default-features = false, features = ["png"] }
|
image = { version = "0.25", optional = true, default-features = false, features = ["png"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
sdl2 = { version = "0.37", optional = true, default-features = false, features = ["use-pkgconfig"] }
|
||||||
|
|
||||||
|
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||||
|
sdl2 = { version = "0.37", optional = true, default-features = false, features = ["bundled", "static-link"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "parkan-render-demo"
|
name = "parkan-render-demo"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# render-demo
|
# render-demo
|
||||||
|
|
||||||
Тестовый рендерер Parkan-моделей на Rust (`SDL2 + OpenGL ES 2.0`).
|
Тестовый рендерер Parkan-моделей на Rust (`SDL2 + OpenGL`: GLES2 с fallback на Core 3.3).
|
||||||
|
|
||||||
## Назначение
|
## Назначение
|
||||||
|
|
||||||
@@ -18,6 +18,16 @@ cargo run -p render-demo --features demo -- \
|
|||||||
--group 0
|
--group 0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### macOS prerequisites
|
||||||
|
|
||||||
|
Для macOS `render-demo` ожидает системный SDL2 через `pkg-config`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install sdl2 pkg-config
|
||||||
|
```
|
||||||
|
|
||||||
|
После этого запускайте той же командой `cargo run ... --features demo`.
|
||||||
|
|
||||||
Параметры:
|
Параметры:
|
||||||
|
|
||||||
- `--archive` (обязательный): NRes-архив с `.msh` entry.
|
- `--archive` (обязательный): NRes-архив с `.msh` entry.
|
||||||
@@ -70,4 +80,4 @@ cargo run -p render-demo --features demo -- \
|
|||||||
## Ограничения
|
## Ограничения
|
||||||
|
|
||||||
- Используется только базовая texture-фаза (без полной material/fx анимации).
|
- Используется только базовая texture-фаза (без полной material/fx анимации).
|
||||||
- Вывод через `glDrawArrays(GL_TRIANGLES)` из расширенного triangle-list (позиции+UV).
|
- Вывод через `glDrawElements(GL_TRIANGLES)` с index-buffer (позиции+UV).
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ struct GpuTexture {
|
|||||||
handle: glow::NativeTexture,
|
handle: glow::NativeTexture,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
enum GlBackend {
|
||||||
|
Gles2,
|
||||||
|
Core33,
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_args() -> Result<Args, String> {
|
fn parse_args() -> Result<Args, String> {
|
||||||
let mut archive = None;
|
let mut archive = None;
|
||||||
let mut model = None;
|
let mut model = None;
|
||||||
@@ -265,35 +271,7 @@ fn run(args: Args) -> Result<(), String> {
|
|||||||
.video()
|
.video()
|
||||||
.map_err(|err| format!("failed to init SDL2 video: {err}"))?;
|
.map_err(|err| format!("failed to init SDL2 video: {err}"))?;
|
||||||
|
|
||||||
{
|
let (window, _gl_ctx, gl_backend) = create_window_and_context(&video, &args)?;
|
||||||
let gl_attr = video.gl_attr();
|
|
||||||
gl_attr.set_context_profile(sdl2::video::GLProfile::GLES);
|
|
||||||
gl_attr.set_context_version(2, 0);
|
|
||||||
gl_attr.set_depth_size(24);
|
|
||||||
gl_attr.set_double_buffer(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut window_builder = video.window(
|
|
||||||
"Parkan Render Demo (SDL2 + OpenGL ES 2.0)",
|
|
||||||
args.width,
|
|
||||||
args.height,
|
|
||||||
);
|
|
||||||
window_builder.opengl();
|
|
||||||
if args.capture.is_some() {
|
|
||||||
window_builder.hidden();
|
|
||||||
} else {
|
|
||||||
window_builder.resizable();
|
|
||||||
}
|
|
||||||
let window = window_builder
|
|
||||||
.build()
|
|
||||||
.map_err(|err| format!("failed to create window: {err}"))?;
|
|
||||||
|
|
||||||
let gl_ctx = window
|
|
||||||
.gl_create_context()
|
|
||||||
.map_err(|err| format!("failed to create OpenGL context: {err}"))?;
|
|
||||||
window
|
|
||||||
.gl_make_current(&gl_ctx)
|
|
||||||
.map_err(|err| format!("failed to make GL context current: {err}"))?;
|
|
||||||
let _ = if args.capture.is_some() {
|
let _ = if args.capture.is_some() {
|
||||||
video.gl_set_swap_interval(0)
|
video.gl_set_swap_interval(0)
|
||||||
} else {
|
} else {
|
||||||
@@ -315,7 +293,7 @@ fn run(args: Args) -> Result<(), String> {
|
|||||||
glow::Context::from_loader_function(|name| video.gl_get_proc_address(name) as *const _)
|
glow::Context::from_loader_function(|name| video.gl_get_proc_address(name) as *const _)
|
||||||
};
|
};
|
||||||
|
|
||||||
let program = unsafe { create_program(&gl)? };
|
let program = unsafe { create_program(&gl, gl_backend)? };
|
||||||
let u_mvp = unsafe { gl.get_uniform_location(program, "u_mvp") };
|
let u_mvp = unsafe { gl.get_uniform_location(program, "u_mvp") };
|
||||||
let u_use_tex = unsafe { gl.get_uniform_location(program, "u_use_tex") };
|
let u_use_tex = unsafe { gl.get_uniform_location(program, "u_use_tex") };
|
||||||
let u_tex = unsafe { gl.get_uniform_location(program, "u_tex") };
|
let u_tex = unsafe { gl.get_uniform_location(program, "u_tex") };
|
||||||
@@ -334,6 +312,7 @@ fn run(args: Args) -> Result<(), String> {
|
|||||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
||||||
gl.bind_buffer(glow::ARRAY_BUFFER, None);
|
gl.bind_buffer(glow::ARRAY_BUFFER, None);
|
||||||
}
|
}
|
||||||
|
let vao = unsafe { create_vertex_layout_if_needed(&gl, gl_backend, vbo, ebo, a_pos, a_uv)? };
|
||||||
|
|
||||||
let gpu_texture = if let Some(texture) = resolved_texture.as_ref() {
|
let gpu_texture = if let Some(texture) = resolved_texture.as_ref() {
|
||||||
Some(unsafe { create_texture(&gl, texture)? })
|
Some(unsafe { create_texture(&gl, texture)? })
|
||||||
@@ -352,6 +331,7 @@ fn run(args: Args) -> Result<(), String> {
|
|||||||
a_uv,
|
a_uv,
|
||||||
vbo,
|
vbo,
|
||||||
ebo,
|
ebo,
|
||||||
|
vao,
|
||||||
gpu_texture.as_ref(),
|
gpu_texture.as_ref(),
|
||||||
mesh.indices.len(),
|
mesh.indices.len(),
|
||||||
&args,
|
&args,
|
||||||
@@ -372,6 +352,7 @@ fn run(args: Args) -> Result<(), String> {
|
|||||||
a_uv,
|
a_uv,
|
||||||
vbo,
|
vbo,
|
||||||
ebo,
|
ebo,
|
||||||
|
vao,
|
||||||
gpu_texture.as_ref(),
|
gpu_texture.as_ref(),
|
||||||
mesh.indices.len(),
|
mesh.indices.len(),
|
||||||
&args,
|
&args,
|
||||||
@@ -384,6 +365,9 @@ fn run(args: Args) -> Result<(), String> {
|
|||||||
if let Some(texture) = gpu_texture {
|
if let Some(texture) = gpu_texture {
|
||||||
gl.delete_texture(texture.handle);
|
gl.delete_texture(texture.handle);
|
||||||
}
|
}
|
||||||
|
if let Some(vao) = vao {
|
||||||
|
gl.delete_vertex_array(vao);
|
||||||
|
}
|
||||||
gl.delete_buffer(ebo);
|
gl.delete_buffer(ebo);
|
||||||
gl.delete_buffer(vbo);
|
gl.delete_buffer(vbo);
|
||||||
gl.delete_program(program);
|
gl.delete_program(program);
|
||||||
@@ -392,6 +376,97 @@ fn run(args: Args) -> Result<(), String> {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_window_and_context(
|
||||||
|
video: &sdl2::VideoSubsystem,
|
||||||
|
args: &Args,
|
||||||
|
) -> Result<(sdl2::video::Window, sdl2::video::GLContext, GlBackend), String> {
|
||||||
|
let candidates = [
|
||||||
|
(GlBackend::Gles2, sdl2::video::GLProfile::GLES, 2, 0),
|
||||||
|
(GlBackend::Core33, sdl2::video::GLProfile::Core, 3, 3),
|
||||||
|
];
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
for (backend, profile, major, minor) in candidates {
|
||||||
|
{
|
||||||
|
let gl_attr = video.gl_attr();
|
||||||
|
gl_attr.set_context_profile(profile);
|
||||||
|
gl_attr.set_context_version(major, minor);
|
||||||
|
gl_attr.set_depth_size(24);
|
||||||
|
gl_attr.set_double_buffer(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut window_builder = video.window(
|
||||||
|
"Parkan Render Demo (SDL2 + OpenGL)",
|
||||||
|
args.width,
|
||||||
|
args.height,
|
||||||
|
);
|
||||||
|
window_builder.opengl();
|
||||||
|
if args.capture.is_some() {
|
||||||
|
window_builder.hidden();
|
||||||
|
} else {
|
||||||
|
window_builder.resizable();
|
||||||
|
}
|
||||||
|
|
||||||
|
let window = match window_builder.build() {
|
||||||
|
Ok(window) => window,
|
||||||
|
Err(err) => {
|
||||||
|
errors.push(format!(
|
||||||
|
"{profile:?} {major}.{minor}: window build failed ({err})"
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let gl_ctx = match window.gl_create_context() {
|
||||||
|
Ok(ctx) => ctx,
|
||||||
|
Err(err) => {
|
||||||
|
errors.push(format!(
|
||||||
|
"{profile:?} {major}.{minor}: context create failed ({err})"
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = window.gl_make_current(&gl_ctx) {
|
||||||
|
errors.push(format!(
|
||||||
|
"{profile:?} {major}.{minor}: make current failed ({err})"
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok((window, gl_ctx, backend));
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(format!(
|
||||||
|
"failed to create OpenGL context. Attempts: {}",
|
||||||
|
errors.join(" | ")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn create_vertex_layout_if_needed(
|
||||||
|
gl: &glow::Context,
|
||||||
|
backend: GlBackend,
|
||||||
|
vbo: glow::NativeBuffer,
|
||||||
|
ebo: glow::NativeBuffer,
|
||||||
|
a_pos: u32,
|
||||||
|
a_uv: u32,
|
||||||
|
) -> Result<Option<glow::NativeVertexArray>, String> {
|
||||||
|
if backend != GlBackend::Core33 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let vao = gl.create_vertex_array().map_err(|e| e.to_string())?;
|
||||||
|
gl.bind_vertex_array(Some(vao));
|
||||||
|
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
|
||||||
|
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(ebo));
|
||||||
|
gl.enable_vertex_attrib_array(a_pos);
|
||||||
|
gl.vertex_attrib_pointer_f32(a_pos, 3, glow::FLOAT, false, 20, 0);
|
||||||
|
gl.enable_vertex_attrib_array(a_uv);
|
||||||
|
gl.vertex_attrib_pointer_f32(a_uv, 2, glow::FLOAT, false, 20, 12);
|
||||||
|
gl.bind_vertex_array(None);
|
||||||
|
Ok(Some(vao))
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_texture(args: &Args, model_name: &str) -> Result<Option<LoadedTexture>, String> {
|
fn resolve_texture(args: &Args, model_name: &str) -> Result<Option<LoadedTexture>, String> {
|
||||||
if args.no_texture {
|
if args.no_texture {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -466,6 +541,7 @@ fn run_capture(
|
|||||||
a_uv: u32,
|
a_uv: u32,
|
||||||
vbo: glow::NativeBuffer,
|
vbo: glow::NativeBuffer,
|
||||||
ebo: glow::NativeBuffer,
|
ebo: glow::NativeBuffer,
|
||||||
|
vao: Option<glow::NativeVertexArray>,
|
||||||
texture: Option<&GpuTexture>,
|
texture: Option<&GpuTexture>,
|
||||||
index_count: usize,
|
index_count: usize,
|
||||||
args: &Args,
|
args: &Args,
|
||||||
@@ -493,6 +569,7 @@ fn run_capture(
|
|||||||
a_uv,
|
a_uv,
|
||||||
vbo,
|
vbo,
|
||||||
ebo,
|
ebo,
|
||||||
|
vao,
|
||||||
texture,
|
texture,
|
||||||
index_count,
|
index_count,
|
||||||
args.width,
|
args.width,
|
||||||
@@ -520,6 +597,7 @@ fn run_interactive(
|
|||||||
a_uv: u32,
|
a_uv: u32,
|
||||||
vbo: glow::NativeBuffer,
|
vbo: glow::NativeBuffer,
|
||||||
ebo: glow::NativeBuffer,
|
ebo: glow::NativeBuffer,
|
||||||
|
vao: Option<glow::NativeVertexArray>,
|
||||||
texture: Option<&GpuTexture>,
|
texture: Option<&GpuTexture>,
|
||||||
index_count: usize,
|
index_count: usize,
|
||||||
args: &Args,
|
args: &Args,
|
||||||
@@ -560,6 +638,7 @@ fn run_interactive(
|
|||||||
a_uv,
|
a_uv,
|
||||||
vbo,
|
vbo,
|
||||||
ebo,
|
ebo,
|
||||||
|
vao,
|
||||||
texture,
|
texture,
|
||||||
index_count,
|
index_count,
|
||||||
w,
|
w,
|
||||||
@@ -602,6 +681,7 @@ unsafe fn draw_frame(
|
|||||||
a_uv: u32,
|
a_uv: u32,
|
||||||
vbo: glow::NativeBuffer,
|
vbo: glow::NativeBuffer,
|
||||||
ebo: glow::NativeBuffer,
|
ebo: glow::NativeBuffer,
|
||||||
|
vao: Option<glow::NativeVertexArray>,
|
||||||
texture: Option<&GpuTexture>,
|
texture: Option<&GpuTexture>,
|
||||||
index_count: usize,
|
index_count: usize,
|
||||||
width: u32,
|
width: u32,
|
||||||
@@ -631,6 +711,16 @@ unsafe fn draw_frame(
|
|||||||
gl.bind_texture(glow::TEXTURE_2D, None);
|
gl.bind_texture(glow::TEXTURE_2D, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(vao) = vao {
|
||||||
|
gl.bind_vertex_array(Some(vao));
|
||||||
|
gl.draw_elements(
|
||||||
|
glow::TRIANGLES,
|
||||||
|
index_count.min(i32::MAX as usize) as i32,
|
||||||
|
glow::UNSIGNED_SHORT,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
gl.bind_vertex_array(None);
|
||||||
|
} else {
|
||||||
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
|
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
|
||||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(ebo));
|
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(ebo));
|
||||||
gl.enable_vertex_attrib_array(a_pos);
|
gl.enable_vertex_attrib_array(a_pos);
|
||||||
@@ -647,6 +737,7 @@ unsafe fn draw_frame(
|
|||||||
gl.disable_vertex_attrib_array(a_pos);
|
gl.disable_vertex_attrib_array(a_pos);
|
||||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
||||||
gl.bind_buffer(glow::ARRAY_BUFFER, None);
|
gl.bind_buffer(glow::ARRAY_BUFFER, None);
|
||||||
|
}
|
||||||
gl.bind_texture(glow::TEXTURE_2D, None);
|
gl.bind_texture(glow::TEXTURE_2D, None);
|
||||||
gl.use_program(None);
|
gl.use_program(None);
|
||||||
}
|
}
|
||||||
@@ -701,8 +792,13 @@ fn save_png(path: &Path, width: u32, height: u32, rgba: Vec<u8>) -> Result<(), S
|
|||||||
.map_err(|err| format!("failed to save PNG {}: {err}", path.display()))
|
.map_err(|err| format!("failed to save PNG {}: {err}", path.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn create_program(gl: &glow::Context) -> Result<glow::NativeProgram, String> {
|
unsafe fn create_program(
|
||||||
let vs_src = r#"
|
gl: &glow::Context,
|
||||||
|
backend: GlBackend,
|
||||||
|
) -> Result<glow::NativeProgram, String> {
|
||||||
|
let (vs_src, fs_src) = match backend {
|
||||||
|
GlBackend::Gles2 => (
|
||||||
|
r#"
|
||||||
attribute vec3 a_pos;
|
attribute vec3 a_pos;
|
||||||
attribute vec2 a_uv;
|
attribute vec2 a_uv;
|
||||||
uniform mat4 u_mvp;
|
uniform mat4 u_mvp;
|
||||||
@@ -711,9 +807,8 @@ void main() {
|
|||||||
v_uv = a_uv;
|
v_uv = a_uv;
|
||||||
gl_Position = u_mvp * vec4(a_pos, 1.0);
|
gl_Position = u_mvp * vec4(a_pos, 1.0);
|
||||||
}
|
}
|
||||||
"#;
|
"#,
|
||||||
|
r#"
|
||||||
let fs_src = r#"
|
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
uniform sampler2D u_tex;
|
uniform sampler2D u_tex;
|
||||||
uniform float u_use_tex;
|
uniform float u_use_tex;
|
||||||
@@ -723,7 +818,32 @@ void main() {
|
|||||||
vec4 texColor = texture2D(u_tex, v_uv);
|
vec4 texColor = texture2D(u_tex, v_uv);
|
||||||
gl_FragColor = mix(base, texColor, u_use_tex);
|
gl_FragColor = mix(base, texColor, u_use_tex);
|
||||||
}
|
}
|
||||||
"#;
|
"#,
|
||||||
|
),
|
||||||
|
GlBackend::Core33 => (
|
||||||
|
r#"#version 330 core
|
||||||
|
in vec3 a_pos;
|
||||||
|
in vec2 a_uv;
|
||||||
|
uniform mat4 u_mvp;
|
||||||
|
out vec2 v_uv;
|
||||||
|
void main() {
|
||||||
|
v_uv = a_uv;
|
||||||
|
gl_Position = u_mvp * vec4(a_pos, 1.0);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"#version 330 core
|
||||||
|
uniform sampler2D u_tex;
|
||||||
|
uniform float u_use_tex;
|
||||||
|
in vec2 v_uv;
|
||||||
|
out vec4 fragColor;
|
||||||
|
void main() {
|
||||||
|
vec4 base = vec4(0.85, 0.90, 1.00, 1.0);
|
||||||
|
vec4 texColor = texture(u_tex, v_uv);
|
||||||
|
fragColor = mix(base, texColor, u_use_tex);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
let program = gl.create_program().map_err(|e| e.to_string())?;
|
let program = gl.create_program().map_err(|e| e.to_string())?;
|
||||||
let vs = gl
|
let vs = gl
|
||||||
|
|||||||
Reference in New Issue
Block a user