give TEA example graphics

This commit is contained in:
Richard Feldman 2022-01-22 19:49:09 -05:00
parent c59aa22402
commit 8f91574629
No known key found for this signature in database
GPG Key ID: 7E4127D1E4241798
24 changed files with 4046 additions and 216 deletions

View File

@ -1 +1 @@
echo
hello-rust

View File

@ -1,14 +1,12 @@
app "hello"
app "hello-rust"
packages { pf: "platform" }
imports []
provides [ render ] to pf
render :
{ width : F32, height : F32 } ->
[
Rectangle { top : F32, left : F32, bottom : F32, right : F32 },
Circle { top : F32, left : F32, radius : F32 },
# Text { top : F32, left : F32, text : Str },
]
render = \window ->
Rectangle { top: 10, left: 10, bottom: 100, right: 100 }
greeting =
hi = "Hello"
name = "World!"
"\(hi), \(name)!\n"
render = greeting

View File

@ -1,8 +0,0 @@
# Command Line Interface (CLI) Example
This is an example of how to make an extremely basic CLI in Roc.
There's not currently much documentation for the CLI platform (which also doesn't support many operations at this point!)
but you can look at [the modules it includes](platform) - for example,
multiple other modules use the [`Task`](platform/Task.roc) module, including the
[`Stdin`](platform/Stdin.roc) and [`Stdout`](platform/Stdout.roc) modules.

File diff suppressed because it is too large Load Diff

View File

@ -4,9 +4,13 @@ version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
links = "app"
# Needed to be able to run on non-Windows systems for some reason. Without this, cargo panics with:
#
# error: DX12 API enabled on non-Windows OS. If your project is not using resolver="2" in Cargo.toml, it should.
resolver = "2"
[lib]
name = "host"
path = "src/lib.rs"
@ -19,5 +23,54 @@ path = "src/main.rs"
[dependencies]
roc_std = { path = "../../../roc_std" }
libc = "0.2"
arrayvec = "0.7.2"
page_size = "0.4.2"
# once winit 0.26 is out, check if copypasta can be updated simultaneously so they use the same versions for their dependencies. This will save build time.
winit = "0.25.0"
wgpu = "0.11.0"
wgpu_glyph = "0.15.1"
glyph_brush = "0.7.2"
log = "0.4.14"
env_logger = "0.9.0"
futures = "0.3.17"
cgmath = "0.18.0"
snafu = { version = "0.6.10", features = ["backtraces"] }
colored = "2.0.0"
pest = "2.1.3"
pest_derive = "2.1.0"
copypasta = "0.7.1"
palette = "0.6.0"
confy = { git = 'https://github.com/rust-cli/confy', features = [
"yaml_conf"
], default-features = false }
serde = { version = "1.0.130", features = ["derive"] }
nonempty = "0.7.0"
fs_extra = "1.2.0"
rodio = { version = "0.14.0", optional = true } # to play sounds
threadpool = "1.8.1"
[package.metadata.cargo-udeps.ignore]
# confy is currently unused but should not be removed
normal = ["confy"]
#development = []
#build = []
[features]
default = []
with_sound = ["rodio"]
[dependencies.bytemuck]
version = "1.7.2"
features = ["derive"]
[workspace]
# Optimizations based on https://deterministic.space/high-performance-rust.html
[profile.release]
lto = "thin"
codegen-units = 1
# debug = true # enable when profiling
[profile.bench]
lto = "thin"
codegen-units = 1

View File

@ -1,24 +1,10 @@
platform "examples/tea"
requires {} {
render :
{ width : F32, height : F32 } ->
[
Rectangle { top : F32, left : F32, bottom : F32, right : F32 },
Circle { top : F32, left : F32, radius : F32 },
# Text { top : F32, left : F32, text : Str },
]
}
platform "examples/hello-world"
requires {} { render : Str }
exposes []
packages {}
imports []
provides [ renderForHost ]
effects fx.Effect {}
renderForHost :
{ width : F32, height : F32 } ->
[
Rectangle { top : F32, left : F32, bottom : F32, right : F32 },
Circle { top : F32, left : F32, radius : F32 },
# Text { top : F32, left : F32, text : Str },
]
renderForHost : Str
renderForHost = render

View File

@ -1,6 +0,0 @@
interface Stdin
exposes [ line ]
imports [ fx.Effect, Task ]
line : Task.Task Str *
line = Effect.after Effect.getLine Task.succeed# TODO FIXME Effect.getLine should suffice

View File

@ -1,8 +0,0 @@
interface Stdout
exposes [ line ]
imports [ fx.Effect, Task.{ Task } ]
# line : Str -> Task.Task {} *
# line = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {})
line : Str -> Task {} *
line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {})

View File

@ -1,94 +0,0 @@
interface Task
exposes [ Task, succeed, fail, await, map, onFail, attempt, forever, loop ]
imports [ fx.Effect ]
Task ok err : Effect.Effect (Result ok err)
forever : Task val err -> Task * err
forever = \task ->
looper = \{ } ->
task
|> Effect.map
\res ->
when res is
Ok _ ->
Step {}
Err e ->
Done (Err e)
Effect.loop {} looper
loop : state, (state -> Task [ Step state, Done done ] err) -> Task done err
loop = \state, step ->
looper = \current ->
step current
|> Effect.map
\res ->
when res is
Ok (Step newState) ->
Step newState
Ok (Done result) ->
Done (Ok result)
Err e ->
Done (Err e)
Effect.loop state looper
succeed : val -> Task val *
succeed = \val ->
Effect.always (Ok val)
fail : err -> Task * err
fail = \val ->
Effect.always (Err val)
attempt : Task a b, (Result a b -> Task c d) -> Task c d
attempt = \effect, transform ->
Effect.after
effect
\result ->
when result is
Ok ok ->
transform (Ok ok)
Err err ->
transform (Err err)
await : Task a err, (a -> Task b err) -> Task b err
await = \effect, transform ->
Effect.after
effect
\result ->
when result is
Ok a ->
transform a
Err err ->
Task.fail err
onFail : Task ok a, (a -> Task ok b) -> Task ok b
onFail = \effect, transform ->
Effect.after
effect
\result ->
when result is
Ok a ->
Task.succeed a
Err err ->
transform err
map : Task a err, (a -> b) -> Task b err
map = \effect, transform ->
Effect.after
effect
\result ->
when result is
Ok a ->
Task.succeed (transform a)
Err err ->
Task.fail err

View File

@ -1,3 +1,3 @@
extern int rust_main();
int main() { return rust_main(); }
int main() { return rust_main(); }

View File

@ -0,0 +1,31 @@
use palette::{FromColor, Hsv, Srgb};
pub type RgbaTup = (f32, f32, f32, f32);
pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0);
pub fn to_wgpu_color((r, g, b, a): RgbaTup) -> wgpu::Color {
wgpu::Color {
r: r as f64,
g: g as f64,
b: b as f64,
a: a as f64,
}
}
pub fn to_slice((r, g, b, a): RgbaTup) -> [f32; 4] {
[r, g, b, a]
}
pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup {
from_hsba(hue, saturation, brightness, 1.0)
}
pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup {
let rgb = Srgb::from_color(Hsv::new(
hue as f32,
(saturation as f32) / 100.0,
(brightness as f32) / 100.0,
));
(rgb.red, rgb.green, rgb.blue, alpha)
}

View File

@ -0,0 +1,168 @@
// Adapted from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen - license information can be found in the COPYRIGHT
// file in the root directory of this distribution.
//
// Thank you, Benjamin!
use super::vertex::Vertex;
use crate::graphics::colors::to_slice;
use crate::graphics::primitives::rect::Rect;
use wgpu::util::{BufferInitDescriptor, DeviceExt};
pub struct QuadBufferBuilder {
vertex_data: Vec<Vertex>,
index_data: Vec<u32>,
current_quad: u32,
}
impl QuadBufferBuilder {
pub fn new() -> Self {
Self {
vertex_data: Vec::new(),
index_data: Vec::new(),
current_quad: 0,
}
}
pub fn push_rect(self, rect: &Rect) -> Self {
let coords = rect.top_left_coords;
self.push_quad(
coords.x,
coords.y,
coords.x + rect.width,
coords.y + rect.height,
to_slice(rect.color),
)
}
pub fn push_quad(
mut self,
min_x: f32,
min_y: f32,
max_x: f32,
max_y: f32,
color: [f32; 4],
) -> Self {
self.vertex_data.extend(&[
Vertex {
position: (min_x, min_y).into(),
color,
},
Vertex {
position: (max_x, min_y).into(),
color,
},
Vertex {
position: (max_x, max_y).into(),
color,
},
Vertex {
position: (min_x, max_y).into(),
color,
},
]);
self.index_data.extend(&[
self.current_quad * 4,
self.current_quad * 4 + 1,
self.current_quad * 4 + 2,
self.current_quad * 4,
self.current_quad * 4 + 2,
self.current_quad * 4 + 3,
]);
self.current_quad += 1;
self
}
pub fn build(self, device: &wgpu::Device) -> (StagingBuffer, StagingBuffer, u32) {
(
StagingBuffer::new(device, &self.vertex_data),
StagingBuffer::new(device, &self.index_data),
self.index_data.len() as u32,
)
}
}
impl Default for QuadBufferBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct RectBuffers {
pub vertex_buffer: wgpu::Buffer,
pub index_buffer: wgpu::Buffer,
pub num_rects: u32,
}
pub fn create_rect_buffers(
gpu_device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
rects: &[Rect],
) -> RectBuffers {
let nr_of_rects = rects.len() as u64;
let vertex_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: Vertex::SIZE * 4 * nr_of_rects,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let u32_size = std::mem::size_of::<u32>() as wgpu::BufferAddress;
let index_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: u32_size * 6 * nr_of_rects,
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let num_rects = {
let mut quad_buffer_builder = QuadBufferBuilder::new();
for rect in rects {
quad_buffer_builder = quad_buffer_builder.push_rect(rect);
}
let (stg_vertex, stg_index, num_indices) = quad_buffer_builder.build(gpu_device);
stg_vertex.copy_to_buffer(encoder, &vertex_buffer);
stg_index.copy_to_buffer(encoder, &index_buffer);
num_indices
};
RectBuffers {
vertex_buffer,
index_buffer,
num_rects,
}
}
pub struct StagingBuffer {
buffer: wgpu::Buffer,
size: wgpu::BufferAddress,
}
impl StagingBuffer {
pub fn new<T: bytemuck::Pod + Sized>(device: &wgpu::Device, data: &[T]) -> StagingBuffer {
StagingBuffer {
buffer: device.create_buffer_init(&BufferInitDescriptor {
contents: bytemuck::cast_slice(data),
usage: wgpu::BufferUsages::COPY_SRC,
label: Some("Staging Buffer"),
}),
size: size_of_slice(data) as wgpu::BufferAddress,
}
}
pub fn copy_to_buffer(&self, encoder: &mut wgpu::CommandEncoder, other: &wgpu::Buffer) {
encoder.copy_buffer_to_buffer(&self.buffer, 0, other, 0, self.size)
}
}
// Taken from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen - license information can be found in the COPYRIGHT
// file in the root directory of this distribution.
//
// Thank you, Benjamin!
pub fn size_of_slice<T: Sized>(slice: &[T]) -> usize {
std::mem::size_of::<T>() * slice.len()
}

View File

@ -0,0 +1,4 @@
pub mod buffer;
pub mod ortho;
pub mod pipelines;
pub mod vertex;

View File

@ -0,0 +1,118 @@
use cgmath::{Matrix4, Ortho};
use wgpu::util::DeviceExt;
use wgpu::{
BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer,
ShaderStages,
};
// orthographic projection is used to transform pixel coords to the coordinate system used by wgpu
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct Uniforms {
// We can't use cgmath with bytemuck directly so we'll have
// to convert the Matrix4 into a 4x4 f32 array
ortho: [[f32; 4]; 4],
}
impl Uniforms {
fn new(w: u32, h: u32) -> Self {
let ortho: Matrix4<f32> = Ortho::<f32> {
left: 0.0,
right: w as f32,
bottom: h as f32,
top: 0.0,
near: -1.0,
far: 1.0,
}
.into();
Self {
ortho: ortho.into(),
}
}
}
// update orthographic buffer according to new window size
pub fn update_ortho_buffer(
inner_width: u32,
inner_height: u32,
gpu_device: &wgpu::Device,
ortho_buffer: &Buffer,
cmd_queue: &wgpu::Queue,
) {
let new_uniforms = Uniforms::new(inner_width, inner_height);
let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Ortho uniform buffer"),
contents: bytemuck::cast_slice(&[new_uniforms]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_SRC,
});
// get a command encoder for the current frame
let mut encoder = gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Resize"),
});
// overwrite the new buffer over the old one
encoder.copy_buffer_to_buffer(
&new_ortho_buffer,
0,
ortho_buffer,
0,
(std::mem::size_of::<Uniforms>() * vec![new_uniforms].as_slice().len())
as wgpu::BufferAddress,
);
cmd_queue.submit(Some(encoder.finish()));
}
#[derive(Debug)]
pub struct OrthoResources {
pub buffer: Buffer,
pub bind_group_layout: BindGroupLayout,
pub bind_group: BindGroup,
}
pub fn init_ortho(
inner_width: u32,
inner_height: u32,
gpu_device: &wgpu::Device,
) -> OrthoResources {
let uniforms = Uniforms::new(inner_width, inner_height);
let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Ortho uniform buffer"),
contents: bytemuck::cast_slice(&[uniforms]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
// bind groups consist of extra resources that are provided to the shaders
let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
label: Some("Ortho bind group layout"),
});
let ortho_bind_group = gpu_device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &ortho_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: ortho_buffer.as_entire_binding(),
}],
label: Some("Ortho bind group"),
});
OrthoResources {
buffer: ortho_buffer,
bind_group_layout: ortho_bind_group_layout,
bind_group: ortho_bind_group,
}
}

View File

@ -0,0 +1,70 @@
use super::ortho::{init_ortho, OrthoResources};
use super::vertex::Vertex;
use std::borrow::Cow;
pub struct RectResources {
pub pipeline: wgpu::RenderPipeline,
pub ortho: OrthoResources,
}
pub fn make_rect_pipeline(
gpu_device: &wgpu::Device,
surface_config: &wgpu::SurfaceConfiguration,
) -> RectResources {
let ortho = init_ortho(surface_config.width, surface_config.height, gpu_device);
let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
bind_group_layouts: &[&ortho.bind_group_layout],
push_constant_ranges: &[],
label: Some("Rectangle pipeline layout"),
});
let pipeline = create_render_pipeline(
gpu_device,
&pipeline_layout,
surface_config.format,
&wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/shader.wgsl"))),
},
);
RectResources { pipeline, ortho }
}
pub fn create_render_pipeline(
device: &wgpu::Device,
layout: &wgpu::PipelineLayout,
color_format: wgpu::TextureFormat,
shader_module_desc: &wgpu::ShaderModuleDescriptor,
) -> wgpu::RenderPipeline {
let shader = device.create_shader_module(shader_module_desc);
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render pipeline"),
layout: Some(layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[Vertex::DESC],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
operation: wgpu::BlendOperation::Add,
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
},
alpha: wgpu::BlendComponent::REPLACE,
}),
write_mask: wgpu::ColorWrites::ALL,
}],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
})
}

View File

@ -0,0 +1,37 @@
// Taken from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen - license information can be found in the COPYRIGHT
// file in the root directory of this distribution.
//
// Thank you, Benjamin!
use cgmath::Vector2;
#[derive(Copy, Clone)]
pub struct Vertex {
pub position: Vector2<f32>,
pub color: [f32; 4],
}
unsafe impl bytemuck::Pod for Vertex {}
unsafe impl bytemuck::Zeroable for Vertex {}
impl Vertex {
pub const SIZE: wgpu::BufferAddress = std::mem::size_of::<Self>() as wgpu::BufferAddress;
pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
array_stride: Self::SIZE,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
// position
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
},
// color
wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x4,
},
],
};
}

View File

@ -0,0 +1,4 @@
pub mod colors;
pub mod lowlevel;
pub mod primitives;
pub mod style;

View File

@ -0,0 +1,2 @@
pub mod rect;
pub mod text;

View File

@ -0,0 +1,9 @@
use cgmath::Vector2;
#[derive(Debug, Copy, Clone)]
pub struct Rect {
pub top_left_coords: Vector2<f32>,
pub width: f32,
pub height: f32,
pub color: (f32, f32, f32, f32),
}

View File

@ -0,0 +1,162 @@
// Adapted from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen - license information can be found in the COPYRIGHT
// file in the root directory of this distribution.
//
// Thank you, Benjamin!
use super::rect::Rect;
use crate::graphics::colors;
use crate::graphics::colors::RgbaTup;
use crate::graphics::style::DEFAULT_FONT_SIZE;
use ab_glyph::{FontArc, Glyph, InvalidFont};
use cgmath::{Vector2, Vector4};
use glyph_brush::OwnedSection;
use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder, GlyphCruncher, Section};
#[derive(Debug)]
pub struct Text<'a> {
pub position: Vector2<f32>,
pub area_bounds: Vector2<f32>,
pub color: RgbaTup,
pub text: &'a str,
pub size: f32,
pub visible: bool,
pub centered: bool,
}
impl<'a> Default for Text<'a> {
fn default() -> Self {
Self {
position: (0.0, 0.0).into(),
area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(),
color: colors::WHITE,
text: "",
size: DEFAULT_FONT_SIZE,
visible: true,
centered: false,
}
}
}
// necessary to get dimensions for caret
pub fn example_code_glyph_rect(glyph_brush: &mut GlyphBrush<()>, font_size: f32) -> Rect {
let code_text = Text {
position: (0.0, 0.0).into(),
area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(),
color: colors::WHITE,
text: "a",
size: font_size,
..Default::default()
};
let layout = layout_from_text(&code_text);
let section = section_from_text(&code_text, layout);
let mut glyph_section_iter = glyph_brush.glyphs_custom_layout(section, &layout);
if let Some(glyph) = glyph_section_iter.next() {
glyph_to_rect(glyph)
} else {
unreachable!();
}
}
pub fn layout_from_text(text: &Text) -> wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker> {
wgpu_glyph::Layout::default().h_align(if text.centered {
wgpu_glyph::HorizontalAlign::Center
} else {
wgpu_glyph::HorizontalAlign::Left
})
}
fn section_from_text<'a>(
text: &'a Text,
layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
) -> wgpu_glyph::Section<'a> {
Section {
screen_position: text.position.into(),
bounds: text.area_bounds.into(),
layout,
..Section::default()
}
.add_text(
wgpu_glyph::Text::new(text.text)
.with_color(Vector4::from(text.color))
.with_scale(text.size),
)
}
pub fn owned_section_from_text(text: &Text) -> OwnedSection {
let layout = layout_from_text(text);
OwnedSection {
screen_position: text.position.into(),
bounds: text.area_bounds.into(),
layout,
..OwnedSection::default()
}
.add_text(
glyph_brush::OwnedText::new(text.text)
.with_color(Vector4::from(text.color))
.with_scale(text.size),
)
}
pub fn owned_section_from_glyph_texts(
text: Vec<glyph_brush::OwnedText>,
screen_position: (f32, f32),
area_bounds: (f32, f32),
layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
) -> glyph_brush::OwnedSection {
glyph_brush::OwnedSection {
screen_position,
bounds: area_bounds,
layout,
text,
}
}
pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) {
let layout = layout_from_text(text);
let section = section_from_text(text, layout);
glyph_brush.queue(section.clone());
}
fn glyph_to_rect(glyph: &wgpu_glyph::SectionGlyph) -> Rect {
let position = glyph.glyph.position;
let px_scale = glyph.glyph.scale;
let width = glyph_width(&glyph.glyph);
let height = px_scale.y;
let top_y = glyph_top_y(&glyph.glyph);
Rect {
top_left_coords: [position.x, top_y].into(),
width,
height,
color: colors::WHITE,
}
}
pub fn glyph_top_y(glyph: &Glyph) -> f32 {
let height = glyph.scale.y;
glyph.position.y - height * 0.75
}
pub fn glyph_width(glyph: &Glyph) -> f32 {
glyph.scale.x * 0.4765
}
pub fn build_glyph_brush(
gpu_device: &wgpu::Device,
render_format: wgpu::TextureFormat,
) -> Result<GlyphBrush<()>, InvalidFont> {
let inconsolata = FontArc::try_from_slice(include_bytes!(
"../../../../../../editor/Inconsolata-Regular.ttf"
))?;
Ok(GlyphBrushBuilder::using_font(inconsolata).build(gpu_device, render_format))
}

View File

@ -0,0 +1,31 @@
struct VertexOutput {
[[location(0)]] color: vec4<f32>;
[[builtin(position)]] position: vec4<f32>;
};
[[block]]
struct Globals {
ortho: mat4x4<f32>;
};
[[group(0), binding(0)]]
var<uniform> u_globals: Globals;
[[stage(vertex)]]
fn vs_main(
[[location(0)]] in_position: vec2<f32>,
[[location(1)]] in_color: vec4<f32>,
) -> VertexOutput {
var out: VertexOutput;
out.position = u_globals.ortho * vec4<f32>(in_position, 0.0, 1.0);
out.color = in_color;
return out;
}
[[stage(fragment)]]
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
return in.color;
}

View File

@ -0,0 +1 @@
pub const DEFAULT_FONT_SIZE: f32 = 30.0;

View File

@ -1,34 +1,21 @@
#![allow(non_snake_case)]
use core::alloc::Layout;
use core::ffi::c_void;
use core::mem::{ManuallyDrop, MaybeUninit};
use libc;
use roc_std::RocStr;
use std::ffi::CStr;
use std::os::raw::c_char;
mod graphics;
mod tea;
extern "C" {
#[link_name = "roc__renderForHost_1_exposed"]
fn roc_render(output: *mut u8) -> ();
#[link_name = "roc__renderForHost_size"]
fn roc_render_size() -> i64;
#[link_name = "roc__renderForHost_1_Fx_caller"]
fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8) -> ();
#[allow(dead_code)]
#[link_name = "roc__renderForHost_1_Fx_size"]
fn size_Fx() -> i64;
#[link_name = "roc__renderForHost_1_Fx_result_size"]
fn size_Fx_result() -> i64;
fn roc_render() -> RocStr;
}
#[no_mangle]
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size)
return libc::malloc(size);
}
#[no_mangle]
@ -38,12 +25,12 @@ pub unsafe extern "C" fn roc_realloc(
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
libc::realloc(c_ptr, new_size)
return libc::realloc(c_ptr, new_size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
return libc::free(c_ptr);
}
#[no_mangle]
@ -71,56 +58,10 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
let size = unsafe { roc_render_size() } as usize;
let layout = Layout::array::<u8>(size).unwrap();
let roc_str = unsafe { roc_render() };
unsafe {
// TODO allocate on the stack if it's under a certain size
let buffer = std::alloc::alloc(layout);
roc_render(buffer);
let result = call_the_closure(buffer);
std::alloc::dealloc(buffer, layout);
dbg!(result);
};
tea::render(roc_str);
// Exit code
0
}
unsafe extern "C" fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
let size = size_Fx_result() as usize;
let layout = Layout::array::<u8>(size).unwrap();
let buffer = std::alloc::alloc(layout) as *mut u8;
call_Fx(
// This flags pointer will never get dereferenced
MaybeUninit::uninit().as_ptr(),
closure_data_ptr as *const u8,
buffer as *mut u8,
);
std::alloc::dealloc(buffer, layout);
0
}
#[no_mangle]
pub extern "C" fn roc_fx_getLine() -> RocStr {
use std::io::{self, BufRead};
let stdin = io::stdin();
let line1 = stdin.lock().lines().next().unwrap().unwrap();
RocStr::from_slice(line1.as_bytes())
}
#[no_mangle]
pub extern "C" fn roc_fx_putLine(line: ManuallyDrop<RocStr>) {
let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
println!("{}", string);
}

View File

@ -0,0 +1,306 @@
use crate::graphics::{
lowlevel::buffer::create_rect_buffers, lowlevel::ortho::update_ortho_buffer,
lowlevel::pipelines, primitives::rect::Rect, primitives::text::build_glyph_brush,
};
use pipelines::RectResources;
use roc_std::RocStr;
use std::error::Error;
use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView};
use winit::{
dpi::PhysicalSize,
event,
event::{Event, ModifiersState},
event_loop::ControlFlow,
platform::run_return::EventLoopExtRunReturn,
};
// Inspired by:
// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, which is licensed under the MIT license
// https://github.com/cloudhead/rgx by Alexis Sellier, which is licensed under the MIT license
//
// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/
fn run_event_loop(title: &str) -> Result<(), Box<dyn Error>> {
// Open window and create a surface
let mut event_loop = winit::event_loop::EventLoop::new();
let mut needs_repaint = true;
let window = winit::window::WindowBuilder::new()
.with_inner_size(PhysicalSize::new(1900.0, 1000.0))
.with_title("The Roc Editor - Work In Progress")
.build(&event_loop)
.unwrap();
let instance = wgpu::Instance::new(wgpu::Backends::all());
let surface = unsafe { instance.create_surface(&window) };
// Initialize GPU
let (gpu_device, cmd_queue) = futures::executor::block_on(async {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.expect(r#"Request adapter
If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#editor
"#);
adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
},
None,
)
.await
.expect("Request device")
});
// Create staging belt and a local pool
let mut staging_belt = wgpu::util::StagingBelt::new(1024);
let mut local_pool = futures::executor::LocalPool::new();
let local_spawner = local_pool.spawner();
// Prepare swap chain
let render_format = wgpu::TextureFormat::Bgra8Unorm;
let mut size = window.inner_size();
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Mailbox,
};
surface.configure(&gpu_device, &surface_config);
let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &surface_config);
let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?;
let is_animating = true;
let mut keyboard_modifiers = ModifiersState::empty();
// Render loop
window.request_redraw();
event_loop.run_return(|event, _, control_flow| {
// TODO dynamically switch this on/off depending on whether any
// animations are running. Should conserve CPU usage and battery life!
if is_animating {
*control_flow = ControlFlow::Poll;
} else {
*control_flow = ControlFlow::Wait;
}
match event {
//Close
Event::WindowEvent {
event: event::WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
//Resize
Event::WindowEvent {
event: event::WindowEvent::Resized(new_size),
..
} => {
size = new_size;
surface.configure(
&gpu_device,
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Mailbox,
},
);
update_ortho_buffer(
size.width,
size.height,
&gpu_device,
&rect_resources.ortho.buffer,
&cmd_queue,
);
}
//Received Character
Event::WindowEvent {
event: event::WindowEvent::ReceivedCharacter(ch),
..
} => {
// let input_outcome_res =
// app_update::handle_new_char(&ch, &mut app_model, keyboard_modifiers);
// if let Err(e) = input_outcome_res {
// print_err(&e)
// } else if let Ok(InputOutcome::Ignored) = input_outcome_res {
// println!("Input '{}' ignored!", ch);
// }
todo!("TODO handle character input");
}
//Keyboard Input
Event::WindowEvent {
event: event::WindowEvent::KeyboardInput { input, .. },
..
} => {
// if let Some(virtual_keycode) = input.virtual_keycode {
// if let Some(ref mut ed_model) = app_model.ed_model_opt {
// if ed_model.has_focus {
// let keydown_res = keyboard_input::handle_keydown(
// input.state,
// virtual_keycode,
// keyboard_modifiers,
// &mut app_model,
// );
// if let Err(e) = keydown_res {
// print_err(&e)
// }
// }
// }
// }
todo!("TODO handle keyboard input");
}
//Modifiers Changed
Event::WindowEvent {
event: event::WindowEvent::ModifiersChanged(modifiers),
..
} => {
keyboard_modifiers = modifiers;
}
Event::MainEventsCleared => window.request_redraw(),
Event::RedrawRequested { .. } => {
// Get a command encoder for the current frame
let mut encoder =
gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Redraw"),
});
let surface_texture = surface
.get_current_texture()
.expect("Failed to acquire next SwapChainTexture");
let view = surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
// for text_section in &rendered_wgpu.text_sections_behind {
// let borrowed_text = text_section.to_borrowed();
// glyph_brush.queue(borrowed_text);
// }
// draw first layer of text
glyph_brush
.draw_queued(
&gpu_device,
&mut staging_belt,
&mut encoder,
&view,
size.width,
size.height,
)
.expect("Failed to draw first layer of text.");
// draw rects on top of first text layer
// draw_rects(
// &rendered_wgpu.rects_front,
// &mut encoder,
// &view,
// &gpu_device,
// &rect_resources,
// wgpu::LoadOp::Load,
// );
// for text_section in &rendered_wgpu.text_sections_front {
// let borrowed_text = text_section.to_borrowed();
// glyph_brush.queue(borrowed_text);
// }
// draw text
glyph_brush
.draw_queued(
&gpu_device,
&mut staging_belt,
&mut encoder,
&view,
size.width,
size.height,
)
.expect("Failed to draw queued text.");
staging_belt.finish();
cmd_queue.submit(Some(encoder.finish()));
surface_texture.present();
// Recall unused staging buffers
use futures::task::SpawnExt;
local_spawner
.spawn(staging_belt.recall())
.expect("Recall staging belt");
local_pool.run_until_stalled();
}
_ => {
*control_flow = winit::event_loop::ControlFlow::Wait;
}
}
});
Ok(())
}
fn draw_rects(
all_rects: &[Rect],
encoder: &mut CommandEncoder,
texture_view: &TextureView,
gpu_device: &wgpu::Device,
rect_resources: &RectResources,
load_op: LoadOp<wgpu::Color>,
) {
let rect_buffers = create_rect_buffers(gpu_device, encoder, all_rects);
let mut render_pass = begin_render_pass(encoder, texture_view, load_op);
render_pass.set_pipeline(&rect_resources.pipeline);
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..));
render_pass.set_index_buffer(
rect_buffers.index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1);
}
fn begin_render_pass<'a>(
encoder: &'a mut CommandEncoder,
texture_view: &'a TextureView,
load_op: LoadOp<wgpu::Color>,
) -> RenderPass<'a> {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachment {
view: texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: load_op,
store: true,
},
}],
depth_stencil_attachment: None,
label: None,
})
}
pub fn render(title: RocStr) {
run_event_loop(title.as_str()).expect("Error running event loop")
}