Skip to content

Instantly share code, notes, and snippets.

@justacec
Created March 29, 2021 12:01
Show Gist options
  • Save justacec/2e34692c0b7b8e30660ac535c5a7457e to your computer and use it in GitHub Desktop.
Save justacec/2e34692c0b7b8e30660ac535c5a7457e to your computer and use it in GitHub Desktop.
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()
}
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
typedef struct {
float x;
float y;
}position;
typedef struct {
float r;
float g;
float b;
}color;
typedef struct {
position p;
color c;
}AAPLVertex;
struct ColorInOut {
float4 position[[position]];
float4 color;
};
vertex ColorInOut vs(constant AAPLVertex * vertex_array[[buffer(0)]], unsigned int vid[[vertex_id]]) {
ColorInOut out;
out.position = float4(float2(vertex_array[vid].p.x, vertex_array[vid].p.y), 0.0, 1.0);
out.color = float4(float3(vertex_array[vid].c.r, vertex_array[vid].c.g, vertex_array[vid].c.b), 0.0);
return out;
}
fragment float4 ps(ColorInOut in [[stage_in]]) {
return in.color;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment