Created
March 29, 2021 12:01
-
-
Save justacec/2e34692c0b7b8e30660ac535c5a7457e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use metal::*; | |
use winit::platform::macos::WindowExtMacOS; | |
use winit::{ | |
event::{Event, WindowEvent}, | |
event_loop::{ControlFlow, EventLoop}, | |
}; | |
use cocoa::{appkit::NSView, base::id as cocoa_id}; | |
use objc::{rc::autoreleasepool, runtime::YES}; | |
use std::{borrow::Borrow, mem}; | |
use rand::prelude::*; | |
use rand::distributions::{Uniform}; | |
use std::time::{Duration, Instant}; | |
//use std::thread::sleep; | |
use std::{cell::RefCell, rc::Rc}; | |
// Declare the data structures needed to carry vertex layout to | |
// metal shading language(MSL) program. Use #[repr(C)], to make | |
// the data structure compatible with C++ type data structure | |
// for vertex defined in MSL program as MSL program is broadly | |
// based on C++ | |
#[repr(C)] | |
#[derive(Debug)] | |
pub struct position(cty::c_float, cty::c_float); | |
#[repr(C)] | |
#[derive(Debug, Copy, Clone)] | |
pub struct color(cty::c_float, cty::c_float, cty::c_float); | |
#[repr(C)] | |
#[derive(Debug)] | |
pub struct AAPLVertex { | |
p: position, | |
c: color, | |
} | |
fn main() { | |
// Create a window for viewing the content | |
let event_loop = EventLoop::new(); | |
let events_loop = winit::event_loop::EventLoop::new(); | |
let size = winit::dpi::LogicalSize::new(800, 800); | |
let window = winit::window::WindowBuilder::new() | |
.with_inner_size(size) | |
.with_title("Metal".to_string()) | |
.with_transparent(true) | |
.with_always_on_top(true) | |
.build(&events_loop) | |
.unwrap(); | |
// Set up the GPU device found in the system | |
let device = Device::system_default().expect("no device found"); | |
println!("Your device is: {}", device.name(),); | |
let library_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) | |
.join("src/shaders.metallib"); | |
// Use the metallib file generated out of .metal shader file | |
let library = device.new_library_with_file(library_path).unwrap(); | |
// The render pipeline generated from the vertex and fragment shaders in the .metal shader file. | |
let pipeline_state = prepare_pipeline_state(&device, &library); | |
// Set the command queue used to pass commands to the device. | |
let command_queue = device.new_command_queue(); | |
// Currently, MetalLayer is the only interface that provide | |
// layers to carry drawable texture from GPU rendaring through metal | |
// library to viewable windows. | |
let layer = MetalLayer::new(); | |
layer.set_device(&device); | |
layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm); | |
layer.set_presents_with_transaction(false); | |
unsafe { | |
let view = window.ns_view() as cocoa_id; | |
view.setWantsLayer(YES); | |
view.setLayer(mem::transmute(layer.as_ref())); | |
} | |
let draw_size = window.inner_size(); | |
layer.set_drawable_size(CGSize::new(draw_size.width as f64, draw_size.height as f64)); | |
/* | |
let vbuf = { | |
let vertex_data = create_vertex_points_for_circle(); | |
let vertex_data = vertex_data.as_slice(); | |
device.new_buffer_with_data( | |
vertex_data.as_ptr() as *const _, | |
(vertex_data.len() * mem::size_of::<AAPLVertex>()) as u64, | |
MTLResourceOptions::CPUCacheModeDefaultCache | MTLResourceOptions::StorageModeManaged, | |
) | |
}; | |
*/ | |
event_loop.run(move |event, _, control_flow| { | |
let levels = Rc::new(get_levels()); | |
let vbuf = { | |
let vertex_data = create_bar(levels.borrow()); | |
let vertex_data = vertex_data.as_slice(); | |
device.new_buffer_with_data( | |
vertex_data.as_ptr() as *const _, | |
(vertex_data.len() * mem::size_of::<AAPLVertex>()) as u64, | |
MTLResourceOptions::CPUCacheModeDefaultCache | MTLResourceOptions::StorageModeManaged, | |
) | |
}; | |
autoreleasepool(|| { | |
// ControlFlow::Wait pauses the event loop if no events are available to process. | |
// This is ideal for non-game applications that only update in response to user | |
// input, and uses significantly less power/CPU time than ControlFlow::Poll. | |
// *control_flow = ControlFlow::Wait; | |
*control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(0, 17_000_000)); | |
match event { | |
Event::WindowEvent { | |
event: WindowEvent::CloseRequested, | |
.. | |
} => { | |
println!("The close button was pressed; stopping"); | |
*control_flow = ControlFlow::Exit | |
} | |
Event::MainEventsCleared => { | |
// Queue a RedrawRequested event. | |
window.request_redraw(); | |
} | |
Event::RedrawRequested(_) => { | |
// It's preferrable to render in this event rather than in MainEventsCleared, since | |
// rendering in here allows the program to gracefully handle redraws requested | |
// by the OS. | |
let drawable = match layer.next_drawable() { | |
Some(drawable) => drawable, | |
None => return, | |
}; | |
// Create a new command buffer for each render pass to the current drawable | |
let command_buffer = command_queue.new_command_buffer(); | |
// Obtain a renderPassDescriptor generated from the view's drawable textures. | |
let render_pass_descriptor = RenderPassDescriptor::new(); | |
prepare_render_pass_descriptor(&render_pass_descriptor, drawable.texture()); | |
// Create a render command encoder. | |
let encoder = | |
command_buffer.new_render_command_encoder(&render_pass_descriptor); | |
encoder.set_render_pipeline_state(&pipeline_state); | |
// Pass in the parameter data. | |
encoder.set_vertex_buffer(0, Some(&vbuf), 0); | |
// Draw the triangles which will eventually form the circle. | |
encoder.draw_primitives(MTLPrimitiveType::Triangle, 0, (levels.len()*6) as u64); | |
encoder.end_encoding(); | |
// Schedule a present once the framebuffer is complete using the current drawable. | |
command_buffer.present_drawable(&drawable); | |
// Finalize rendering here & push the command buffer to the GPU. | |
command_buffer.commit(); | |
} | |
_ => (), | |
} | |
}); | |
}); | |
} | |
fn get_levels() -> Vec<f32> { | |
let mut rng = rand::thread_rng(); | |
let dist = Uniform::new_inclusive(0_f32, 1_f32); | |
(&mut rng).sample_iter(dist).take(10).collect() | |
} | |
fn create_bar(levels: &Vec<f32>) -> Vec<AAPLVertex> { | |
let mut v: Vec<AAPLVertex> = Vec::new(); | |
let n_levels = levels.len(); | |
let color_low = color(0.0, 0.0, 0.0); | |
let color_high = color(0.0, 1.0, 0.0); | |
let width = 8_f32 / ((5.0 * n_levels as f32) + 1.0); | |
let gap = width / 4.0; | |
for i in 0..n_levels { | |
let xmin = -1.0_f32 + ((i as f32 + 1.0) * gap) + i as f32 * width; | |
let xmax = xmin + width; | |
let ymin = -1.0_f32; | |
let ymax = ymin + 2.0 * levels[i]; | |
let color_top = color(color_high.0*levels[i], color_high.1*levels[i], color_high.2*levels[i]); | |
v.push(AAPLVertex { | |
p: position(xmin, ymin), | |
c: color_low | |
}); | |
v.push(AAPLVertex{ | |
p: position(xmin, ymax), | |
c: color_top | |
}); | |
v.push(AAPLVertex{ | |
p: position(xmax, ymin), | |
c: color_low | |
}); | |
v.push(AAPLVertex{ | |
p: position(xmax, ymin), | |
c: color_low | |
}); | |
v.push(AAPLVertex{ | |
p: position(xmin, ymax), | |
c: color_top | |
}); | |
v.push(AAPLVertex{ | |
p: position(xmax, ymax), | |
c: color_top | |
}); | |
} | |
v | |
} | |
// If we want to draw a circle, we need to draw it out of the three primitive | |
// types available with metal framework. Triangle is used in this case to form | |
// the circle. If we consider a circle to be total of 360 degree at center, we | |
// can form small triangle with one point at origin and two points at the | |
// perimeter of the circle for each degree. Eventually, if we can take enough | |
// triangle virtices for total of 360 degree, the triangles together will | |
// form a circle. This function captures the triangle vertices for each degree | |
// and push the co-ordinates of the vertices to a rust vector | |
fn create_vertex_points_for_circle() -> Vec<AAPLVertex> { | |
let mut v: Vec<AAPLVertex> = Vec::new(); | |
let origin_x: f32 = 0.0; | |
let origin_y: f32 = 0.0; | |
// Size of the circle | |
let circle_size = 0.5f32; | |
for i in 0..720 { | |
let y = i as f32; | |
// Get the X co-ordinate of each point on the perimeter of circle | |
let position_x: f32 = y.to_radians().cos() * 100.0; | |
let position_x: f32 = position_x.trunc() / 100.0; | |
// Set the size of the circle | |
let position_x: f32 = position_x * circle_size; | |
// Get the Y co-ordinate of each point on the perimeter of circle | |
let position_y: f32 = y.to_radians().sin() * 100.0; | |
let position_y: f32 = position_y.trunc() / 100.0; | |
// Set the size of the circle | |
let position_y: f32 = position_y * circle_size; | |
v.push(AAPLVertex { | |
p: position(position_x, position_y), | |
c: color(0.2, 0.3, 0.5), | |
}); | |
if (i + 1) % 2 == 0 { | |
// For each two points on perimeter, push one point of origin | |
v.push(AAPLVertex { | |
p: position(origin_x, origin_y), | |
c: color(0.2, 0.7, 0.4), | |
}); | |
} | |
} | |
v | |
} | |
fn prepare_render_pass_descriptor(descriptor: &RenderPassDescriptorRef, texture: &TextureRef) { | |
let color_attachment = descriptor.color_attachments().object_at(0).unwrap(); | |
color_attachment.set_texture(Some(texture)); | |
color_attachment.set_load_action(MTLLoadAction::Clear); | |
// Setting a background color | |
color_attachment.set_clear_color(MTLClearColor::new(0.0, 0.0, 0.0, 0.5)); | |
color_attachment.set_store_action(MTLStoreAction::Store); | |
} | |
fn prepare_pipeline_state(device: &Device, library: &Library) -> RenderPipelineState { | |
let vert = library.get_function("vs", None).unwrap(); | |
let frag = library.get_function("ps", None).unwrap(); | |
let pipeline_state_descriptor = RenderPipelineDescriptor::new(); | |
pipeline_state_descriptor.set_vertex_function(Some(&vert)); | |
pipeline_state_descriptor.set_fragment_function(Some(&frag)); | |
let mut thing0 = pipeline_state_descriptor | |
.color_attachments() | |
.object_at(0) | |
.unwrap(); | |
let mut thing1 = pipeline_state_descriptor | |
.color_attachments() | |
.object_at(1) | |
.unwrap(); | |
for i in 0..8 { | |
let mut thingi = pipeline_state_descriptor | |
.color_attachments() | |
.object_at(i) | |
.unwrap(); | |
thingi.set_pixel_format(MTLPixelFormat::BGRA8Unorm); | |
thingi.set_destination_rgb_blend_factor(MTLBlendFactor::OneMinusSourceAlpha); | |
thingi.set_destination_alpha_blend_factor(MTLBlendFactor::OneMinusSourceAlpha); | |
thingi.set_blending_enabled(true); | |
} | |
/* | |
thing0.set_pixel_format(MTLPixelFormat::BGRA8Unorm); | |
thing0.set_destination_rgb_blend_factor(MTLBlendFactor::OneMinusSourceAlpha); | |
thing0.set_destination_alpha_blend_factor(MTLBlendFactor::OneMinusSourceAlpha); | |
thing0.set_blending_enabled(true); | |
thing1.set_pixel_format(MTLPixelFormat::BGRA8Unorm); | |
thing1.set_destination_rgb_blend_factor(MTLBlendFactor::OneMinusSourceAlpha); | |
thing1.set_destination_alpha_blend_factor(MTLBlendFactor::OneMinusSourceAlpha); | |
thing1.set_blending_enabled(true); | |
*/ | |
println!("Render Pipeline Props {:?}", pipeline_state_descriptor.color_attachments().object_at(0).unwrap()); | |
device | |
.new_render_pipeline_state(&pipeline_state_descriptor) | |
.unwrap() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment