Skip to content

Commit

Permalink
Use consistent code syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
kvark committed Dec 27, 2018
1 parent 0f30828 commit 2f425cd
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 50 deletions.
56 changes: 28 additions & 28 deletions _posts/2014-08-27-shader-param.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,42 +21,42 @@ We wanted gfx-rs to be low level and flexible, to provide an equally convenient
_Disclaimer_: I haven't worked closely with most of these engines, so any corrections are welcome!

* UDK
{% highlight Javascript %}
```js
theMaterialInstance.SetVectorParameterValue('color', FLinearColor(0.0,0.0,0.0,1.0));
{% endhighlight %}
```
* Unity3D
{% highlight C# %}
```c#
Properties {
_Color ("color", Vector) = (0,0,0,0)
}
{% endhighlight %}
{% highlight C %}
```
```c
renderer.material.SetVector("_Color", Vector4(0,0,0,1));
{% endhighlight %}
```
* Irrlight
{% highlight C++ %}
```cpp
if (UseHighLevelShaders)
services->setVertexShaderConstant("color", reinterpret_cast<f32*>(&col), 4);
else //note magic constants!
services->setVertexShaderConstant(reinterpret_cast<f32*>(&col), 9, 1);
{% endhighlight %}
```
* Ogre3D
{% highlight C++ %}
```cpp
fragment_program_ref GroundPS
{
param_named color float4 0
}
{% endhighlight %}
{% highlight C++ %}
```
```cpp
pParams = pPass->getFragmentProgramParameters();
pParams->setNamedConstant("color", Ogre::Vector4(0.0,0.0,0.0,1.0));
{% endhighlight %}
```
* Horde3D
{% highlight C++ %}
```cpp
Horde3D::setMaterialUniform(m_Material, "color", 0.0, 0.0, 0.0, 1.0);
{% endhighlight %}
```
* Three.js
{% highlight JavaScript %}
```js
var uniforms = {
amplitude: {
type: 'v4',
Expand All @@ -69,28 +69,28 @@ var shaderMaterial = new THREE.MeshShaderMaterial({
vertexShader: vShader.text(),
fragmentShader: fShader.text()
});
{% endhighlight %}
```
* gfx-rs (for comparison)
{% highlight Rust %}
```rust
#[shader_param(MyBatch)]
struct Params {
color: [f32, ..4],
}
let data = Params {
color: [0.0, 0.0, 0.0, 1.0],
};
{% endhighlight %}
```

### SYF 101
SYF: Shoot Yourself in the Foot = "to do or say something that causes problems for you".

Notice how almost every implementation requires you to specify the parameter name as a string. This forces the engine to go through all known parameters and compare them with your string. Obviously, this work is wasted for any subsequent calls. It is also a violation of the [DRY](http://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principle and a potential hazard: every time you ask to match the parameter by name, there is a chance of error (parameter not found because you copy-pasted the name wrong?).

In some engines, you can get a handle to the parameter like this:
{% highlight Rust %}
```rust
let var_color = program.find_parameter("color");
program.set_param_vec4(var_color, [0.0, 0.0, 0.0, 1.0]);
{% endhighlight %}
```
This is a bit more verbose, and partially solves the problem, but clearly "color" is still repeated twice here (as a string and a part of the variable name). Besides, another hazard remains - what if a given parameter has an incompatible type with what shader expects?

Three.js comes the closest to being safe - your variable name is used to query the shader, and the type can be verified inside `MeshShaderMaterial` call. Note, however, that in _JavaScript_ you can change the variable type at run-time, which raises the SYF factor significantly.
Expand All @@ -104,14 +104,14 @@ We are using a procedural macro in Rust to generate the following code at compil
3. Implementation of `fill_params()` - a function that fills up the parameter value, which can be uploaded to GPU.
This is all done behind the `shader_param` attribute:
4. Creates a type alias to the `RefBatch<L ,T>`, named `MyBatch` (see the macro parameter).
{% highlight Rust %}
```rust
#[shader_param(MyBatch)]
struct MyParam {
color: [f32, ..4],
}
{% endhighlight %}
```
Generated code:
{% highlight Rust %}
```rust
struct MyParam {
color: [f32, ..4],
}
Expand Down Expand Up @@ -147,24 +147,24 @@ impl ::gfx::shade::ShaderParam<_MyParamLink> for MyParam {
}
}
}
{% endhighlight %}
```

The end product of this macro is a `MyBatch` type that we can use to create batches with `MyParam` parameters:
{% highlight Rust %}
```rust
let batch: MyBatch = context.batch(...).unwrap();
{% endhighlight %}
```
The `unwrap()` here ignores these possible errors (listing only those related to shader parameters):
* the structure doesn't provide a parameter that shader needs
* a shader parameter is not covered by the structure
* some parameter type is not compatible between the shader and the structure

Later, you provide the `MyParam` instance by reference for every draw call with this batch:
{% highlight Rust %}
```rust
let data = MyParam {
color: [0.0, 0.0, 1.0, 0.0],
};
renderer.draw((&batch, &context, &data), &frame);
{% endhighlight %}
```
Notice that the data is decoupled from the batch right until the draw call, yet the batch has all the type guarantees about data safety of the shader parameters, thus `draw()` can not fail.

### Serializable solution
Expand Down
12 changes: 6 additions & 6 deletions _posts/2015-03-01-device.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ An abstract device has different types associated with it: buffer, texture, samp
2. In the resource management routines (`create_buffer()` and such), which belong to the device.

Since both `CommandBuffer` and the `Device` needed access to all the resource types, we decided to enclose them into a separate `Resources` trait, which became an associated type for our command buffers and devices:
{% highlight Rust %}
```rust
pub trait Resources: {
type Buffer;
type ArrayBuffer;
Expand All @@ -22,10 +22,10 @@ pub trait Resources: {
type Texture;
type Sampler;
}
{% endhighlight %}
```

Here is a part of the `Device` trait:
{% highlight Rust %}
```rust
pub trait Device {
type Resources: Resources;
type Mapper: Clone + RawMapping;
Expand All @@ -36,10 +36,10 @@ pub trait Device {
fn create_sampler(&mut self, info: tex::SamplerInfo) -> SamplerHandle<Self::Resources>;
...
}
{% endhighlight %}
```

The GL backend implements `Resources` with a phantom enum:
{% highlight Rust %}
```rust
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum GlResources {}

Expand All @@ -53,7 +53,7 @@ impl Resources for GlResources {
type Texture = Texture;
type Sampler = Sampler;
}
{% endhighlight %}
```


The migration was started by @bjz in a long series of pull requests ([#564](https://github.com/gfx-rs/gfx-rs/issues/564), [#589](https://github.com/gfx-rs/gfx-rs/issues/589), [#590](https://github.com/gfx-rs/gfx-rs/issues/590), [#592](https://github.com/gfx-rs/gfx-rs/issues/592), [#593](https://github.com/gfx-rs/gfx-rs/issues/593), [#597](https://github.com/gfx-rs/gfx-rs/issues/597)) and finished by @kvark ([`gfx-rs`/#598](https://github.com/gfx-rs/gfx-rs/pull/598), [`gfx-rs`/#609](https://github.com/gfx-rs/gfx-rs/pull/609), [`gfx_device_gl`/#1](https://github.com/gfx-rs/gfx_device_gl/pull/1)). The result - [`gfx_device_gl`](https://github.com/gfx-rs/gfx_device_gl) is a separate crate now that `gfx` itself doesn't depend on. Examples are moved into [their own home](https://github.com/gfx-rs/gfx_examples) as well. Even the macro-generated implementations for shader parameters and vertex formats are API-independent now.
Expand Down
32 changes: 16 additions & 16 deletions _posts/2016-01-22-pso.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ How do we define it? One observation is that all of these 3 structs just carry s

### Gfx-rs Solution
The user can define a PSO in one place with a macro:
{% highlight Rust %}
```rust
use gfx::format::{Depth, Rgba8}; // Used texture formats
gfx_pipeline_base!( pipe {
vertex: gfx::VertexBuffer<Vertex>, // `Vertex` implements `gfx::Structure`
Expand All @@ -29,9 +29,9 @@ gfx_pipeline_base!( pipe {
pixel_color: gfx::RenderTarget<Rgba8>,
depth: gfx::DepthTarget<Depth>,
});
{% endhighlight %}
```
The `gfx_pipeline_base` macro generates a trio of `Data`, `Meta`, and `Init` structs (inside the new module `pipe`) from this definiton, where the user-specified types are exactly what the `Meta` part is going to have. The other two generated structs will look like this:
{% highlight Rust %}
```rust
use gfx::handle::{Buffer, ShaderResourceView, Sampler, RenderTargetView, DepthStencilView};
struct Data {
vertex: Buffer<Vertex>,
Expand All @@ -49,12 +49,12 @@ struct Init<'a> {
pixel_color: &'a str,
depth: gfx::state::Depth,
}
{% endhighlight %}
```
Since the `Init` one will need to be passed for PSO construction by the user, it's clear that our solution doesn't require (or even allow) the user to specify any redundant information, or miss anything. Let's say the user wants to enable blending for the `pixel_color`. All that needs to be done is changing the meta type from `RenderTarget<T>` into `BlendTarget<T>`. This would not change the corresponding `Data` component (which will still be `RenderTargetView<R, T>`), but it would add a mandatory `gfx::state::Blend` to the `Init` struct field.

### In-place Init
For simple applications the PSO initialization format is often known in advance. To save the user from a burden of instantiating the `Init` struct, we provide a convenient extended macro to specify the values in place:
{% highlight Rust %}
```rust
gfx_pipeline!( pipe {
vertex: gfx::VertexBuffer<Vertex> = (),
const_locals: gfx::ConstantBuffer<Local> = "Locals",
Expand All @@ -66,55 +66,55 @@ gfx_pipeline!( pipe {
write: false,
},
});
{% endhighlight %}
```
This extended version will also generate the trio of structs, but in addition have the `Init::new()` method to give you an instance right away! Here is an example code for PSO creation after this macro is invoked:
{% highlight Rust %}
```rust
let pso = factory.create_pipeline_state( // defined in `gfx::traits::FactoryExt`
&shaders, // `gfx::ShaderSet<R>` - has all the shaders
gfx::Primitive::PointList, // primitive topology
gfx::state::Rasterizer::new_fill(gfx::state::CullFace::Nothing),
Init::new() // our new shiny initializer
).unwrap();
{% endhighlight %}
```
There is a simpler version of this function too:
{% highlight Rust %}
```rust
let pso = factory.create_pipeline_simple( // defined in `gfx::traits::FactoryExt`
&vertex_shader, &fragment_shader,
gfx::state::CullFace::Nothing, Init::new()
).unwrap();
{% endhighlight %}
```

### Drawing
The resulting type will be `gfx::PipelineState<pipe::Meta>`, but fortunately the compiler can infer that for you. Using this PSO is rather trivial - the user is only required to construct the `Data` portion themselves:
{% highlight Rust %}
```rust
let data = pipe::Data {
vertex: .., const_locals: ..,
tex_diffuse: .., buf_noise: ..,
pixel_color: .., depth: ..,
};
let slice = gfx::mesh::Slice {...};
encoder.draw(&slice, &pso, &data);
{% endhighlight %}
```
This is rather minimalistic, but, more importantly, there is next to no opportunity to shoot yourself in the foot! All the inputs of the PSO are guaranteed at compile time. Performance-wise the solution is also perfect - all the mapping from the user data and the PSO inputs is already ready for us (contained in the `Meta`, which is constructed during PSO creation), so the actual binding doesn't involve any lookups.

### Structure
Some PSO components operate on structured data. Namely, vertex buffers and constant buffers are supposed to map to Rust structures. In order to assist the user in defining one, we have a special macro:
{% highlight Rust %}
```rust
gfx_vertex_struct!(Vertex {
x@ _x: i8,
y@ _y: f32,
//shader_name@ field_name: type,
});
{% endhighlight %}
```
The macro will create `Vertex` struct that implements `gfx::Structure<gfx::format::Format>`, allowing it to be used as a generic parameter to vertex buffers. The rust fields are then guaranteed to map the corresponding shader fields, at run-time during PSO creation.

A similar macro is introduced for the constant buffers, it implements `gfx::Structure<gfx::shade::ConstFormat>`:
{% highlight Rust %}
```rust
gfx_constant_struct!(Local {
x: i8,
y: f32,
});
{% endhighlight %}
```

## Analysis
PSO is the new paradigm of Gfx-rs interface. It deprecates a lot of the old core concepts (`Batch`, `Output`, `Mesh`, and others), and cleans up the internal structure quite a bit. The [implementation](https://github.com/gfx-rs/gfx/pull/828) can almost be seen as a rewrite of Gfx-rs as we knew it. Moreover, it is perfectly compatible with DX12 and Metal, while still allowing older APIs to emulate PSOs efficiently (see our GL backend).
Expand Down

0 comments on commit 2f425cd

Please sign in to comment.