Skip to content

iOS example #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions rendy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,11 @@ required-features = ["base", "wsi-winit", "shader-compiler"]
name = "source_shaders"
required-features = ["base", "wsi-winit", "shader-compiler"]

[[example]]
name = "ios_triangle"
path = "examples/ios_triangle/lib.rs"
crate-type = ["staticlib"]
required-features = ["base", "wsi-winit", "shader-compiler", "metal"]

[package.metadata.docs.rs]
features = ["full"]
63 changes: 63 additions & 0 deletions rendy/examples/ios_triangle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Rendy iOS Triangle Example

This is a simple triangle example which aims to show a very basic application running on winit, displaying a triangle with rendy.

The Xcode project files are not included in this directory but the steps to configure a project to use the resulting Rust library are described below.

## Prerequisites

```
rustup target add aarch64-apple-ios
```

## Building

```
cd rendy/
cargo build --release --target aarch64-apple-ios --example ios_triangle --features metal
```

The build currently fails with a CMake issue complaining about the C compiler not being able to compile and run a simple program.
For now you have to open up `CMakeCache.txt` in the target directory and replace `CMAKE_OSX_SYSROOT` with this value:

```
CMAKE_OSX_SYSROOT:PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.sdk
```

Some links to reference about this oddity here:

[https://github.com/paritytech/rust-snappy/pull/10/files](https://github.com/paritytech/rust-snappy/pull/10/files)
[https://stackoverflow.com/questions/52879026/cmake-cross-compile-on-macos-adds-macos-sdk-to-isysroot-in-flags-make/52879604#52879604](https://stackoverflow.com/questions/52879026/cmake-cross-compile-on-macos-adds-macos-sdk-to-isysroot-in-flags-make/52879604#52879604)

* Open Xcode
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been adding examples with the Xcode project in them as seen in rust-windowing/glutin#1233. I admit that it's a bit annoying to setup but you can also make it run in CI that way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to take a look at that, I haven't worked on this in quite awhile so I need to refresh my memory.

* Make a new project
* Delete `AppDelegate.swift`
* Delete `SceneDelegate.swift` (if it exists, this is an iOS 13 SDK thing)
* Delete `ContentView.swift` (if it exists, this is an iOS 13 SDK thing)
* Delete "Application Scene Manifest" key from `Info.plist` (if it exists, this is an iOS 13 SDK thing)

#### Under Project Settings -> General
* Clear out "Main interface"

#### Under Project Settings -> Build phases -> Link Binary With Libraries
* Copy `$RUST_PROJECT/target/aarch64-apple-ios/release/libios_triangle.a` to `$IOS_PROJECT/ios-triangle/libs`
* Open `$IOS_PROJECT/libs` in Finder and drag `libios_triangle.a` to the "Link Binary With Libraries" section to add it
* Click the + and add `libc++.tbd`, `Metal.framework`, and `UIKit.framework` as well
* Set the "Objective-C Bridging Header" to `$IOS_PROJECT/ios-triangle/include/ios_triangle.h`

#### Under Project Settings -> Build Settings -> Search Paths
* Copy `$RUST_PROJECT/rendy/examples/ios_triangle/ios_triangle.h` to `$IOS_PROJECT/ios-triangle/include`
* Double click on "Header Search Paths" and add `$IOS_PROJECT/ios-triangle/include`
* Make sure "Library Search Paths" is also populated with `$IOS_PROJECT/ios-triangle/libs`
* Set "Enable Bitcode" to `NO`

#### In the project file explorer
* Add a new Swift file named `main.swift` with the contents of `main.swift` in this directory
* Don't create a bridging header
* Copy `$RUST_PROJECT/rendy/examples/ios_triangle/ios_triangle.h` to `$IOS_PROJECT/ios-triangle/include/ios_triangle.h`

Finally to build the Xcode project, set your build target to "Generic iOS Device" and select Product -> Build

You'll need to set a "development team" under "Signing & Capabilities" in order to successfully build and run.

When developing, you'll want some kind of script to copy `$RUST_PROJECT/target/aarch64-apple-ios/release/libios_triangle.a` to `$IOS_PROJECT/ios-triangle/libs` every time you build it.
6 changes: 6 additions & 0 deletions rendy/examples/ios_triangle/ios_triangle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef ios_triangle_h
#define ios_triangle_h

void run_app();

#endif /* ios_triangle_h */
287 changes: 287 additions & 0 deletions rendy/examples/ios_triangle/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
//!
//! The mighty triangle example, used in an iOS app.
//! This examples shows a colored triangle on a white background.
//! Nothing fancy. Just to prove that `rendy` works.
//!

use rendy::{
command::{Families, QueueId, RenderPassEncoder},
factory::{Config, Factory},
graph::{render::*, Graph, GraphBuilder, GraphContext, NodeBuffer, NodeImage},
hal,
memory::Dynamic,
mesh::PosColor,
resource::{Buffer, BufferInfo, DescriptorSetLayout, Escape, Handle},
shader::{ShaderKind, SourceLanguage, SourceShaderInfo, SpirvShader},
wsi::winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
},
};

#[cfg(feature = "spirv-reflection")]
use rendy::shader::SpirvReflection;

#[cfg(not(feature = "spirv-reflection"))]
use rendy::mesh::AsVertex;

#[cfg(feature = "dx12")]
type Backend = rendy::dx12::Backend;

#[cfg(feature = "metal")]
type Backend = rendy::metal::Backend;

#[cfg(feature = "vulkan")]
type Backend = rendy::vulkan::Backend;

lazy_static::lazy_static! {
static ref VERTEX: SpirvShader = SourceShaderInfo::new(
include_str!("shader.vert"),
concat!(env!("CARGO_MANIFEST_DIR"), "/examples/triangle/shader.vert").into(),
ShaderKind::Vertex,
SourceLanguage::GLSL,
"main",
).precompile().unwrap();

static ref FRAGMENT: SpirvShader = SourceShaderInfo::new(
include_str!("shader.frag"),
concat!(env!("CARGO_MANIFEST_DIR"), "/examples/triangle/shader.frag").into(),
ShaderKind::Fragment,
SourceLanguage::GLSL,
"main",
).precompile().unwrap();

static ref SHADERS: rendy::shader::ShaderSetBuilder = rendy::shader::ShaderSetBuilder::default()
.with_vertex(&*VERTEX).unwrap()
.with_fragment(&*FRAGMENT).unwrap();
}

#[cfg(feature = "spirv-reflection")]
lazy_static::lazy_static! {
static ref SHADER_REFLECTION: SpirvReflection = SHADERS.reflect().unwrap();
}

#[derive(Debug, Default)]
struct TriangleRenderPipelineDesc;

#[derive(Debug)]
struct TriangleRenderPipeline<B: hal::Backend> {
vertex: Option<Escape<Buffer<B>>>,
}

impl<B, T> SimpleGraphicsPipelineDesc<B, T> for TriangleRenderPipelineDesc
where
B: hal::Backend,
T: ?Sized,
{
type Pipeline = TriangleRenderPipeline<B>;

fn depth_stencil(&self) -> Option<hal::pso::DepthStencilDesc> {
None
}

fn load_shader_set(&self, factory: &mut Factory<B>, _aux: &T) -> rendy_shader::ShaderSet<B> {
SHADERS.build(factory, Default::default()).unwrap()
}

fn vertices(
&self,
) -> Vec<(
Vec<hal::pso::Element<hal::format::Format>>,
hal::pso::ElemStride,
hal::pso::VertexInputRate,
)> {
#[cfg(feature = "spirv-reflection")]
return vec![SHADER_REFLECTION
.attributes_range(..)
.unwrap()
.gfx_vertex_input_desc(hal::pso::VertexInputRate::Vertex)];

#[cfg(not(feature = "spirv-reflection"))]
return vec![PosColor::vertex().gfx_vertex_input_desc(hal::pso::VertexInputRate::Vertex)];
}

fn build<'a>(
self,
_ctx: &GraphContext<B>,
_factory: &mut Factory<B>,
_queue: QueueId,
_aux: &T,
buffers: Vec<NodeBuffer>,
images: Vec<NodeImage>,
set_layouts: &[Handle<DescriptorSetLayout<B>>],
) -> Result<TriangleRenderPipeline<B>, gfx_hal::pso::CreationError> {
assert!(buffers.is_empty());
assert!(images.is_empty());
assert!(set_layouts.is_empty());

Ok(TriangleRenderPipeline { vertex: None })
}
}

impl<B, T> SimpleGraphicsPipeline<B, T> for TriangleRenderPipeline<B>
where
B: hal::Backend,
T: ?Sized,
{
type Desc = TriangleRenderPipelineDesc;

fn prepare(
&mut self,
factory: &Factory<B>,
_queue: QueueId,
_set_layouts: &[Handle<DescriptorSetLayout<B>>],
_index: usize,
_aux: &T,
) -> PrepareResult {
if self.vertex.is_none() {
#[cfg(feature = "spirv-reflection")]
let vbuf_size = SHADER_REFLECTION.attributes_range(..).unwrap().stride as u64 * 3;

#[cfg(not(feature = "spirv-reflection"))]
let vbuf_size = PosColor::vertex().stride as u64 * 3;

let mut vbuf = factory
.create_buffer(
BufferInfo {
size: vbuf_size,
usage: hal::buffer::Usage::VERTEX,
},
Dynamic,
)
.unwrap();

unsafe {
// Fresh buffer.
factory
.upload_visible_buffer(
&mut vbuf,
0,
&[
PosColor {
position: [0.0, -0.5, 0.0].into(),
color: [1.0, 0.0, 0.0, 1.0].into(),
},
PosColor {
position: [0.5, 0.5, 0.0].into(),
color: [0.0, 1.0, 0.0, 1.0].into(),
},
PosColor {
position: [-0.5, 0.5, 0.0].into(),
color: [0.0, 0.0, 1.0, 1.0].into(),
},
],
)
.unwrap();
}

self.vertex = Some(vbuf);
}

PrepareResult::DrawReuse
}

fn draw(
&mut self,
_layout: &B::PipelineLayout,
mut encoder: RenderPassEncoder<'_, B>,
_index: usize,
_aux: &T,
) {
let vbuf = self.vertex.as_ref().unwrap();
unsafe {
encoder.bind_vertex_buffers(0, Some((vbuf.raw(), 0)));
encoder.draw(0..3, 0..1);
}
}

fn dispose(self, _factory: &mut Factory<B>, _aux: &T) {}
}

fn run(
event_loop: EventLoop<()>,
mut factory: Factory<Backend>,
mut families: Families<Backend>,
graph: Graph<Backend, ()>,
) {
let started = std::time::Instant::now();

let mut frame = 0u64;
let elapsed = started.elapsed();
let mut graph = Some(graph);

event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
_ => {}
},
Event::EventsCleared => {
factory.maintain(&mut families);
if let Some(ref mut graph) = graph {
graph.run(&mut factory, &mut families, &());
frame += 1;
}
}
_ => {}
}

if *control_flow == ControlFlow::Exit && graph.is_some() {
let elapsed_ns = elapsed.as_secs() * 1_000_000_000 + elapsed.subsec_nanos() as u64;

log::info!(
"Elapsed: {:?}. Frames: {}. FPS: {}",
elapsed,
frame,
frame * 1_000_000_000 / elapsed_ns
);

graph.take().unwrap().dispose(&mut factory, &());
}
});
}

#[no_mangle]
pub extern "C" fn run_app() {
env_logger::Builder::from_default_env()
.filter_module("triangle", log::LevelFilter::Trace)
.init();

let config: Config = Default::default();

let (mut factory, mut families): (Factory<Backend>, _) = rendy::factory::init(config).unwrap();

let event_loop = EventLoop::new();

let window = WindowBuilder::new()
.with_title("Rendy example")
.build(&event_loop)
.unwrap();

let surface = factory.create_surface(&window);

let mut graph_builder = GraphBuilder::<Backend, ()>::new();

graph_builder.add_node(
TriangleRenderPipeline::builder()
.into_subpass()
.with_color_surface()
.into_pass()
.with_surface(
surface,
Some(hal::command::ClearValue {
color: hal::command::ClearColor {
float32: [1.0, 1.0, 1.0, 1.0],
},
}),
),
);

let graph = graph_builder
.build(&mut factory, &mut families, &())
.unwrap();

run(event_loop, factory, families, graph);
}
3 changes: 3 additions & 0 deletions rendy/examples/ios_triangle/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Foundation

run()
11 changes: 11 additions & 0 deletions rendy/examples/ios_triangle/shader.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(early_fragment_tests) in;

layout(location = 0) in vec4 frag_color;
layout(location = 0) out vec4 color;

void main() {
color = frag_color;
}
11 changes: 11 additions & 0 deletions rendy/examples/ios_triangle/shader.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(location = 0) in vec3 pos;
layout(location = 1) in vec4 color;
layout(location = 0) out vec4 frag_color;

void main() {
frag_color = color;
gl_Position = vec4(pos, 1.0);
}