Skip to content

Commit

Permalink
0.0.7 Textured Cube
Browse files Browse the repository at this point in the history
- **!IMPORTANT!** removes implicit viewport transformation, this results in:
  - flipped y-screenspace direction
  - default depth now works as expected
- **!BREAKING CHANGE!** Device::draw() now requires a descriptor_set
- **!BREAKING CHANGE!** renames 'Device::create_buffer()' to 'Device::create_cpu_accessible_buffer()'
- adds basic_ and inverse_ depth tests to GraphicalPassBuilder
- adds descriptor sets
  - adds GraphicalPass::start_persistent_descriptor_set()
- adds image support
  - adds Device::create_immutable_image_from_iter()
  - adds Device::create_sampler() and Device::create_simple_linear_repeat_sampler()
- adds phong_cube example
  • Loading branch information
technomunk committed Dec 7, 2019
2 parents 9ec09e0 + a28b44d commit 6571d5e
Show file tree
Hide file tree
Showing 20 changed files with 782 additions and 37 deletions.
26 changes: 19 additions & 7 deletions changelog.md
@@ -1,22 +1,34 @@
# Changelog

## 0.0.7 Textured Cube

- **!IMPORTANT!** removes implicit viewport transformation, this results in:
- flipped y-screenspace direction
- default depth now works as expected
- **!BREAKING CHANGE!** device.draw() now requires a descriptor_set
- **!BREAKING CHANGE!** renames 'device.create_buffer()' to 'device.create_cpu_accessible_buffer()'
- adds basic_ and inverse_ depth tests to GraphicalPassBuilder
- introduces descriptor_sets
- adds phong_cube example
- forward vulkano::Image items

## 0.0.6 Building Passes in Frames

- **!BREAKING CHANGE!** refactors Drawing device into 2 sub-states:
- *Frame* - active frame
- *PassInFrame* - an active graphical pass within a frame
- **!BREAKING CHANGE!** refactor GraphicalPass to be struct and not a trait
- change the example to handle failing resizing device by skipping a frame
- change the example to work with breaking changes
- update *'vulkano'* dependency to 0.16.0
- move re-exports to sub-projects
- **!BREAKING CHANGE!** refactors GraphicalPass to be struct and not a trait
- changes the example to handle failing resizing device by skipping a frame
- changes the example to work with breaking changes
- updates *'vulkano'* dependency to 0.16.0
- moves re-exports to sub-projects
- vulkano is now exported by gaclen::graphics
- winit is now exported by gaclen::window
- gaclen::window also directly exports winit items
- create a split gaclen_shader project that re-exports a tweaked version of vulkano_shader! macro
- creates a split gaclen_shader project that re-exports a tweaked version of vulkano_shader! macro
- this drops the necessity of depending on vulkano
- vulkano can be used from gaclen::graphics::vulkano
- create GraphicalPassBuilder for initializing a GraphicalPass
- creates GraphicalPassBuilder for initializing a GraphicalPass

## 0.0.5 PresentMode

Expand Down
1 change: 1 addition & 0 deletions gaclen/Cargo.toml
Expand Up @@ -25,4 +25,5 @@ vulkano-win = "0.16.0" # vulkan-winit linkage

[dev-dependencies]
cgmath = "0.17.0" # linear algebra library
image = "0.22.3"
gaclen_shader = { path = "../gaclen_shader", version = "0.0.6" }
65 changes: 65 additions & 0 deletions gaclen/examples/phong_cube/geometry.rs
@@ -0,0 +1,65 @@
use gaclen::graphics::device::Device as GaclenDevice;
use gaclen::graphics::vulkano::memory::DeviceMemoryAllocError;
use gaclen::graphics::vulkano::buffer::CpuAccessibleBuffer;
use std::sync::Arc;

#[derive(Clone, Default)]
pub struct Vertex {
position: [f32; 3],
normal: [f32; 3],
uv: [f32; 2],
}

gaclen::graphics::impl_vertex!(Vertex, position, normal, uv);

pub fn generate_cube(device: &GaclenDevice) -> Result<Arc<CpuAccessibleBuffer<[Vertex]>>, DeviceMemoryAllocError> {
device.create_cpu_accessible_buffer([
// X-
Vertex { position: [ -0.5, 0.5, -0.5 ], normal: [ -1.0, 0.0, 0.0 ], uv: [ 1.0, 1.0 ] },
Vertex { position: [ -0.5, -0.5, -0.5 ], normal: [ -1.0, 0.0, 0.0 ], uv: [ 0.0, 1.0 ] },
Vertex { position: [ -0.5, 0.5, 0.5 ], normal: [ -1.0, 0.0, 0.0 ], uv: [ 1.0, 0.0 ] },
Vertex { position: [ -0.5, 0.5, 0.5 ], normal: [ -1.0, 0.0, 0.0 ], uv: [ 1.0, 0.0 ] },
Vertex { position: [ -0.5, -0.5, -0.5 ], normal: [ -1.0, 0.0, 0.0 ], uv: [ 0.0, 1.0 ] },
Vertex { position: [ -0.5, -0.5, 0.5 ], normal: [ -1.0, 0.0, 0.0 ], uv: [ 0.0, 0.0 ] },

// X+
Vertex { position: [ 0.5, -0.5, -0.5 ], normal: [ 1.0, 0.0, 0.0 ], uv: [ 1.0, 1.0 ] },
Vertex { position: [ 0.5, 0.5, -0.5 ], normal: [ 1.0, 0.0, 0.0 ], uv: [ 0.0, 1.0 ] },
Vertex { position: [ 0.5, 0.5, 0.5 ], normal: [ 1.0, 0.0, 0.0 ], uv: [ 0.0, 0.0 ] },
Vertex { position: [ 0.5, 0.5, 0.5 ], normal: [ 1.0, 0.0, 0.0 ], uv: [ 0.0, 0.0 ] },
Vertex { position: [ 0.5, -0.5, 0.5 ], normal: [ 1.0, 0.0, 0.0 ], uv: [ 1.0, 0.0 ] },
Vertex { position: [ 0.5, -0.5, -0.5 ], normal: [ 1.0, 0.0, 0.0 ], uv: [ 1.0, 1.0 ] },

// Y-
Vertex { position: [ -0.5, -0.5, -0.5 ], normal: [ 0.0, -1.0, 0.0 ], uv: [ 0.0, 1.0 ] },
Vertex { position: [ 0.5, -0.5, -0.5 ], normal: [ 0.0, -1.0, 0.0 ], uv: [ 1.0, 1.0 ] },
Vertex { position: [ 0.5, -0.5, 0.5 ], normal: [ 0.0, -1.0, 0.0 ], uv: [ 1.0, 0.0 ] },
Vertex { position: [ 0.5, -0.5, 0.5 ], normal: [ 0.0, -1.0, 0.0 ], uv: [ 1.0, 0.0 ] },
Vertex { position: [ -0.5, -0.5, 0.5 ], normal: [ 0.0, -1.0, 0.0 ], uv: [ 0.0, 0.0 ] },
Vertex { position: [ -0.5, -0.5, -0.5 ], normal: [ 0.0, -1.0, 0.0 ], uv: [ 0.0, 1.0 ] },

// Y+
Vertex { position: [ -0.5, 0.5, -0.5 ], normal: [ 0.0, 1.0, 0.0 ], uv: [ 1.0, 1.0 ] },
Vertex { position: [ 0.5, 0.5, 0.5 ], normal: [ 0.0, 1.0, 0.0 ], uv: [ 0.0, 0.0 ] },
Vertex { position: [ 0.5, 0.5, -0.5 ], normal: [ 0.0, 1.0, 0.0 ], uv: [ 0.0, 1.0 ] },
Vertex { position: [ 0.5, 0.5, 0.5 ], normal: [ 0.0, 1.0, 0.0 ], uv: [ 0.0, 0.0 ] },
Vertex { position: [ -0.5, 0.5, -0.5 ], normal: [ 0.0, 1.0, 0.0 ], uv: [ 1.0, 1.0 ] },
Vertex { position: [ -0.5, 0.5, 0.5 ], normal: [ 0.0, 1.0, 0.0 ], uv: [ 1.0, 0.0 ] },

// Z-
Vertex { position: [ -0.5, -0.5, -0.5 ], normal: [ 0.0, 0.0, -1.0 ], uv: [ 1.0, 1.0 ] },
Vertex { position: [ -0.5, 0.5, -0.5 ], normal: [ 0.0, 0.0, -1.0 ], uv: [ 1.0, 0.0 ] },
Vertex { position: [ 0.5, 0.5, -0.5 ], normal: [ 0.0, 0.0, -1.0 ], uv: [ 0.0, 0.0 ] },
Vertex { position: [ 0.5, 0.5, -0.5 ], normal: [ 0.0, 0.0, -1.0 ], uv: [ 0.0, 0.0 ] },
Vertex { position: [ 0.5, -0.5, -0.5 ], normal: [ 0.0, 0.0, -1.0 ], uv: [ 0.0, 1.0 ] },
Vertex { position: [ -0.5, -0.5, -0.5 ], normal: [ 0.0, 0.0, -1.0 ], uv: [ 1.0, 1.0 ] },

// Z+
Vertex { position: [ -0.5, -0.5, 0.5 ], normal: [ 0.0, 0.0, 1.0 ], uv: [ 1.0, 1.0 ] },
Vertex { position: [ 0.5, 0.5, 0.5 ], normal: [ 0.0, 0.0, 1.0 ], uv: [ 0.0, 0.0 ] },
Vertex { position: [ -0.5, 0.5, 0.5 ], normal: [ 0.0, 0.0, 1.0 ], uv: [ 1.0, 0.0 ] },
Vertex { position: [ 0.5, 0.5, 0.5 ], normal: [ 0.0, 0.0, 1.0 ], uv: [ 0.0, 0.0 ] },
Vertex { position: [ -0.5, -0.5, 0.5 ], normal: [ 0.0, 0.0, 1.0 ], uv: [ 1.0, 1.0 ] },
Vertex { position: [ 0.5, -0.5, 0.5 ], normal: [ 0.0, 0.0, 1.0 ], uv: [ 0.0, 1.0 ] },
].iter().cloned())
}
182 changes: 182 additions & 0 deletions gaclen/examples/phong_cube/main.rs
@@ -0,0 +1,182 @@
//! A phong-lit, textured Cube example.
//!
//! This example showcases all functionality required to render a believable 3D world.
//! It is limited to a single cube, a real scene would be a lot more complex, likely with additional helper code and resources.
//!
//! Please note, that because of screen-space coordinate mismatch between OpenGL and Vulkan the `up` coordinate and triangle-faces are reversed.

extern crate gaclen;

mod shaders;
mod geometry;

use gaclen::graphics;

use gaclen::window::{
WindowBuilder,
EventsLoop,
Event, WindowEvent,
};

use std::sync::Arc;

fn main() {
let mut frame_count: u64 = 0;
let start_time = std::time::Instant::now();

let mut events_loop = EventsLoop::new();
let window = std::sync::Arc::new(
WindowBuilder::new()
.with_title("Cube example")
.with_dimensions((1280, 720).into())
.with_min_dimensions((1280, 720).into())
.build(&events_loop).unwrap()
);

let context = graphics::context::Context::new().unwrap();
let mut device = graphics::device::Device::new(&context, window.clone(), graphics::device::PresentMode::Immediate).unwrap();
println!("Initialized device: {:?}", device);

let albedo_pass = {
let vs = shaders::vertex::Shader::load(&device).unwrap();
let fs = shaders::fragment::Shader::load(&device).unwrap();

graphics::pass::GraphicalPass::start()
.single_buffer_input::<geometry::Vertex>()
.vertex_shader(vs.main_entry_point(), ())
.fragment_shader(fs.main_entry_point(), ())
.basic_depth_test()
.front_face_clockwise()
.cull_back()
.build_present_pass(&device).unwrap()
};

let geometry = geometry::generate_cube(&device).unwrap();

let transform_buffer_pool = device.create_cpu_buffer_pool::<shaders::vertex::ty::TransformData>(graphics::BufferUsage::all());
let light_buffer_pool = device.create_cpu_buffer_pool::<shaders::fragment::ty::LightData>(graphics::BufferUsage::all());

let texture = {
let image = image::open("gaclen/examples/phong_cube/texture.png").unwrap().to_rgba();
let (width, height) = image.dimensions();
let dimensions = graphics::Dimensions::Dim2d { width, height };
let image_data = image.into_raw(); // to_rgba() returns Vec<u8> backed container

device.create_immutable_image_from_iter(image_data.iter().cloned(), dimensions, graphics::PixelFormat::R8G8B8A8Srgb).unwrap()
};

let sampler = device.create_simple_linear_repeat_sampler().unwrap();

let light = {
let data = shaders::fragment::ty::LightData {
position: [2.0, -2.0, 1.0, 0.0],
ambient: [0.1; 4],
diffuse: [0.8, 0.8, 0.8, 2.0],
specular: [1.0; 4], // 4th component is power
};
light_buffer_pool.next(data).unwrap()
};

let light_descriptor_set = Arc::new(albedo_pass.start_persistent_descriptor_set(1)
.add_buffer(light).unwrap()
.add_sampled_image(texture, sampler).unwrap()
.build().unwrap());

let mut recreate_swapchain = false;

let mut rotation_enabled = false;
let mut last_x = 0;
let mut last_y = 0;
let mut object_rotation = cgmath::Quaternion::new(1.0, 0.0, 0.0, 0.0);

let mut running = true;
while running {
if recreate_swapchain {
// Sometimes the swapchain fails to create :(
match device.resize_for_window(&window) {
Ok(()) => (),
Err(graphics::ResizeError::Swapchain(_)) => {
println!("Failed to resize window, skipping frame!");
continue;
},
Err(err) => panic!(err),
};
recreate_swapchain = false;
}

let clear_color = [0.0, 0.0, 0.0, 1.0];

let transform = {
let data = transform(object_rotation.clone(), window.get_inner_size().unwrap().into());
transform_buffer_pool.next(data).unwrap()
};

let transform_descriptor_set = Arc::new(albedo_pass.start_persistent_descriptor_set(0)
.add_buffer(transform).unwrap()
.build().unwrap());

let after_frame = device.begin_frame().unwrap()
.begin_pass(&albedo_pass, vec![clear_color.into(), 1f32.into()])
.draw(vec![geometry.clone()], (transform_descriptor_set, light_descriptor_set.clone()), ())
.finish_pass()
.finish_frame();

device = match after_frame {
Ok(device) => device,
Err((device, err)) => {
if err == graphics::device::FrameFinishError::Flush(gaclen::graphics::vulkano::sync::FlushError::OutOfDate) { recreate_swapchain = true; };
device
},
};

frame_count += 1;

events_loop.poll_events(|event| {
match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => running = false,
Event::WindowEvent { event: WindowEvent::Resized(_), .. } => recreate_swapchain = true,
Event::WindowEvent { event: WindowEvent::MouseInput{state, button, .. }, .. } => {
rotation_enabled = (button == gaclen::window::MouseButton::Right) && state == gaclen::window::ElementState::Pressed;

}
Event::WindowEvent { event: WindowEvent::CursorMoved{ position, .. }, .. } => {
let (x, y) = position.into();

if rotation_enabled {
let (width, height) : (f64, f64) = window.get_inner_size().unwrap().into();
let delta_x = (x as f32 - last_x as f32) / width as f32;
let delta_y = (y as f32 - last_y as f32) / height as f32;
let delta : cgmath::Quaternion<_> = cgmath::Euler::new(cgmath::Rad(0.0), cgmath::Rad(delta_y), -cgmath::Rad(delta_x)).into();
object_rotation = delta * object_rotation;
}

last_x = x;
last_y = y;
}
_ => ()
}
});
}

let run_duration = start_time.elapsed().as_secs_f64();
let fps: f64 = frame_count as f64 / run_duration;

println!("Produced {} frames over {:.2} seconds ({:.2} avg fps)", frame_count, run_duration, fps);
}

// Ideally the view and projection matrices would be found by some 'Camera' concept.
fn transform(rotation: cgmath::Quaternion<f32>, window_resolution: (u32, u32)) -> shaders::vertex::ty::TransformData {
let aspect = window_resolution.0 as f32 / window_resolution.1 as f32;

let model: cgmath::Matrix4<f32> = rotation.into();
let proj: cgmath::Matrix4<f32> = cgmath::PerspectiveFov { fovy: cgmath::Deg(40.0).into(), aspect, near: 0.1, far: 4.0 }.into();

shaders::vertex::ty::TransformData {
model: model.into(),
view: cgmath::Matrix4::look_at(
cgmath::Point3 { x: 3.0, y: 0.0, z: 0.0 },
cgmath::Point3 { x: 0.0, y: 0.0, z: 0.0 },
cgmath::Vector3 { x: 0.0, y: 0.0, z: -1.0 }).into(),
proj: proj.into(),
}
}
34 changes: 34 additions & 0 deletions gaclen/examples/phong_cube/shader.frag
@@ -0,0 +1,34 @@
#version 450

layout(location = 0) in vec3 in_position;
layout(location = 1) in vec3 in_normal;
layout(location = 2) in vec2 in_uv;

layout(set = 1, binding = 0) uniform LightData {
vec4 position;
vec4 ambient;
vec4 diffuse;
vec4 specular;
} u_light;
layout(set = 1, binding = 1) uniform sampler2D u_texture;

layout(location = 0) out vec4 out_color;

void main() {
vec3 light_dir = u_light.position.xyz - in_position;
float light_dist = length(light_dir);
light_dist = light_dist * light_dist;
light_dir = normalize(light_dir);
vec3 normal = normalize(in_normal);

float lambertian = max(dot(light_dir, normal), 0.0);
float specular = 0.0;

// TODO: specularity

vec3 color =
u_light.ambient.xyz +
u_light.diffuse.xyz * lambertian * u_light.diffuse.w / light_dist +
u_light.specular.xyz * specular / light_dist;
out_color = vec4(color * texture(u_texture, in_uv).xyz, 1);
}
23 changes: 23 additions & 0 deletions gaclen/examples/phong_cube/shader.vert
@@ -0,0 +1,23 @@
#version 450

layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
layout(location = 2) in vec2 uv;

layout(set = 0, binding = 0) uniform TransformData {
mat4 model;
mat4 view;
mat4 proj;
} u_transform;

layout(location = 0) out vec3 out_position;
layout(location = 1) out vec3 out_normal;
layout(location = 2) out vec2 out_uv;

void main() {
gl_Position = u_transform.proj * u_transform.view * u_transform.model * vec4(position, 1.0);

out_position = (u_transform.model * vec4(position, 1.0)).xyz;
out_normal = (u_transform.model * vec4(normal, 0.0)).xyz;
out_uv = uv;
}
18 changes: 18 additions & 0 deletions gaclen/examples/phong_cube/shaders.rs
@@ -0,0 +1,18 @@
pub mod vertex {
gaclen_shader::shader!{
ty: "vertex",
path: "examples/phong_cube/shader.vert",
}

// force recompilation on changes in shader source
const _: &'static [u8] = include_bytes!("shader.vert");
}
pub mod fragment {
gaclen_shader::shader!{
ty: "fragment",
path: "examples/phong_cube/shader.frag",
}

// force recompilation on changes in shader source
const _: &'static [u8] = include_bytes!("shader.frag");
}
Binary file added gaclen/examples/phong_cube/texture.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 6571d5e

Please sign in to comment.