OSX Progress

This was a pretty good week of coding. After implementing some core platform specific code and moving the Windows OpenGL code to Mac OSX, the game rendered on the screen without any issue.


Banished on OSX

Well, I shouldn’t say without any issue. It runs at less than 1 frame per second and water isn’t rendering correctly. There’s still no sound and no input, no Steam integration and it lacks the ability to resize the window and shut down. But I’m getting there. It’s good progress.

Working on Mac and doing this port has been a good experience. This week I hope to head back and spend time on the current Beta version a bit to work out some issues, then get back to the Mac version.

In the mean time here’s some thoughts on working on Mac. I’ve been using Windows exclusively (except for console programming) for a long time, and OSX is a new system to me, so don’t be offended if I’m getting things wrong. :)

Using a Mac

I don’t know anything about OSX. When I started I could open the web browser, and that’s about it. I stumbled with the user interface for a bit, couldn’t figure out how an ‘All Files’ category was useful when browsing finder windows, but then realized I could open a term window and that I was really using a unix-like system with a user interface that wasn’t X Windows. Good deal, I can do that.

So after that, file organization and using the machine was easy, and I was comfortably editing files using vi. Apparently my vi command muscle memory hasn’t totally faded.

Development Environment: Xcode

Xcode isn’t too bad. It was fairly intuitive to bring the Banished source into it, setup the required compiler settings and get to work. I mostly turned off it’s auto formatting since it does things that don’t go with my code style, and the intellesense is a bit overdone, but overall it’s an IDE that gets the job done.

Languages: Objective-C

When I look at Objective-C and the general Cocoa libraries, I feel like I’m looking at a foreign language I haven’t used in 20 years. I bought a book on it to help out. I mostly get it, but I just feel like I’m missing a fundamental knowledge base. Luckily there’s not that much of it I have to write or use before it jumps straight into the C++ code that’s common to all platforms.

I just need to spend more time with it. And read the book.

Code portability

I had the best intentions of writing my game engine using portable C++. I didn’t do anything crazy with the language, and any platform specific chuncks were tucked away in their own files to be replaced per platform.

While that was a good start, I don’t think it’s possible to actually write cross platform code until you’re compiling the code with multiple compilers. Even before I could get to writing the OSX specifics, I had many errors and warnings that clang presented that the Microsoft compiler just overlooked.

Most of these had to do with templates that expected the code inside them not to be compiled until they were instantiated. The Microsoft compiler has that behavior, while clang does not. There’s command line flags for a compatibility mode, but I’d rather just fix the issues so that other compilers won’t treat the errors the same way.

Once the common code was compiling cleanly, I started writing things specific to OSX. Memory management, file I/O, timers, date handling, threading, etc. What is nice, is being a unixy environment, a ton of the platform specific code will also work on Linux and Steam Box. When I get to working on Linux more fully it should go quickly.

I did have a few issues that required changes to both the Windows and Mac code bases to make sharing more code possible, but it was an easy refactor.

I’m glad I spent the time to write a common shader language, switch to UTF8, and make other changes to the engine to make porting easier. I could have just ported as the code was, but it would have been a lot more labor intensive and bug prone.

Currently the toolset for Banished only works on Windows – so it’s really just the engine and game code that are being written for OSX. This is a little annoying, as all the data has to be compiled on the PC, and then the Mac just reads it. I should convert everything to work on all platforms, so that I don’t have to flipflop machines, but this would take a lot of additional effort.


The OpenGL code I wrote for Windows came over smoothly, without any compile errors. Although I did have to write the platform specific startup code to create pixel formats and OpenGL contexts. I’m not using SDL or any other library to hide those platform differences.

I unfortunately have two different sets of the same GL code now. The Windows code is nearly identical, but since Windows supports multiple renderers (DX9/DX11/GL/etc) there’s some abstractions and separation of data that doesn’t exist on Mac.

Copy-pasted-slightly-edited code like this annoys me a bit but at the moment I don’t have a good way to abstract away the differences. It’s not terrible, but a little frustrating to have to update two sets of the same code when bug fixes are made. At least the Linux build should share the code 100%.

If nothing else, it was satisfying to actually see the title screen come up on the first try after bringing over the OpenGL code. If I was writing for a console (or other graphics API) it’d probably take an entire week just getting the first triangle on the screen.

The fact that it’s running at 1 FPS is a little disheartening – I know the GPU is fast enough. I’ve got a Windows machine with an Intel Iris Pro 5000 that runs Banished just fine, which is the same graphics hardware in my MacBook Pro. I’ve got my suspicions as to whats going on but I have a bunch of testing ahead of me to make sure I fix the issue properly.

More Coding

I’ve got a bit more coding and learning about Mac to do before the port is done. I don’t expect the remaining tasks to be too arduous – playing sounds, reading input, and compiling the Steam library should be easy. ish. Maybe.


Beta Build!

I just uploaded the new beta build (This is version 1.0.5 Beta, build 151026) to Steam, and also a patch for those of you that have the stand alone version. I’m going to be providing a bit of detail here for people that want to beta test and modders that want to try out the changes to the mod kit.

So first things first.

Where do I get the beta build?

If you have the game on Steam, go into your game library and right click on Banished. Select properties, and then in the windows that opens, select the BETAs tab. Select the drop down and pick Beta Test for 1.0.5.

If you don’t use Steam, you can download the patch here: BanishedPatch_1.0.4_To_1.0.5.151026.Beta.zip. Note that you need to apply the patch to version 1.0.4. Previous versions of the game won’t work with this patch. Once downloaded, just unzip the archive into the folder where you have Banished installed. This is usually C:\Program Files\Shining Rock Software\Banished\.

If you’re into modding, you can get the beta mod kit here: BanishedKit_1.0.5.151026.Beta.zip.

What’s changed in this beta build?

This beta build is mostly to test major engine changes that have been made to support multiple platforms for Banished and future games. It also contains a few bug fixes, and some changes to the mod kit. Note that save games and mods made with 1.0.5 beta will not work in 1.0.4. In fact they’ll probably crash the game if you try to move saves or mods over. Old mods and saves should work fine with the new version.

Here’s the change list:

  • – UTF8 is now used instead of USC2.
  • – Resource files can be in UTF8, USC2, UTF16, big and little endian. They’ll be converted to UTF8 on load.
  • – Memory usage allowance has been increased to 1 gigabyte, which should allow for larger mods.
  • – All materials now use custom shading language SRSL instead of HLSL.
    • – Any mods with custom materials will need to be modified to point to the new shaders and/or use SRSL.
  • – Math library can now be compiled without the need for SIMD instructions.
  • – OpenGL is now supported (but isn’t currently being released with the PC version)
  • – Data compilation is now in a separate DLL – CompileWin.dll – this can be swapped out for other platforms (consoles, mac, linux, etc)
  • – Shader compiler is now in it’s own DLL. Video DX9/DX11/GL dlls are no longer required for compiling shaders.
  • – Added safety code to check for invalid and dangling pointers – this should make catching hard to find and rare issues easier.
  • – Sped up mod details dialog for massive mods that have 10000’s of files included. This should make looking at conflicts and uploading to Steam workshop easier.
  • – Beta Mods and Mods newer than the currently released version can no longer be uploaded to Steam Workshop.
  • – Nvidia and AMD GPUs in laptops should now be auto selected for use, instead of an Intel Integrated card.
  • – Textile limit is now available for modders to use.
    • – Cropfields, Fishing, Forester, Hunters, Orchards, and Pastures now have a configurable resource limit.
    • – Livestock has a resource limit for the by product they make (eggs, wool, milk, etc) Note that if a by product isn’t created because of the resource limit, the icon won’t appear above the building.
    • – Added textile to the Status Bar, Resource Limit window, and Town Hall UI
    • – Added graphs for textiles to Town Hall UI

What if I find a problem with the Beta Build?

If you find a problem, I’d like to hear about it. You can submit bugs on the forum in the new beta sub forum. Or through the regular Support methods.

One major issue I’d like to know about if it occurs is a new debugging feature to help track down object reference errors. If you get an error that looks like one of the following images, I really want to know about it.


It’s pretty important that I fix these if they occur, otherwise the game state can become corrupt and make save games possibly invalid after load, causing crashes. If it happens, please include the crash.dmp and your save game. Especially if you can reproduce the problem reliably.

This additional error checking might slow down the game 5-10%, but it will go away for full release builds.

What changes happened in the mod kit?

First, Steam Workshop uploads are disabled in the beta build. Since 1.0.5 mods don’t work with version 1.0.4, I won’t be allowing uploads until 1.0.5 is updated for everyone.

Second, Textile is now a working resource type and limit.

All resource producing buildings can all specify which resource limit makes them stop producing. This allows a crop field to be limited by the textile limit and stop producing cotton, while a food producing crop field keeps working. It looks like this:

CropFieldDescription cropfield
	ResourceLimit _resourceLimit = Textile;
	float _growthPercentOnTend = 0.007;

The _resourceLimit variable now exists for Crops, Fishing Huts, Foresters, Hunters, Orchards and Pastures.

For pastures that have an animal that produces a byproduct (like sheep producing wool), you can now specify a resource limit on the animal. For example, the sheep resource looks like this now:

LivestockDescription livestock 
	ComponentDescription _additionalRawMaterial = "Template\RawMaterialWool.rsc";
	float _additionalCreateInMonths = 3.0;

	// this should probably be Textile, but is being left as food so the base game
	// is unchanged. Modders will definately want to change this...
	ResourceLimit _resourceLimit = Food; // Textile 

If your mod uses new materials, there will be some work to update them. Instead of what was previously there, the engine uses a custom shading language. Old materials should still load and work, but to build a new mod you’ll have to rework them. There’s some details of the new shading language here.

What does the future hold?

My current plan is that while this build is being tested by the community (big thank you!), I’ll be working on the Mac and Linux builds pretty much exclusively. With the Mac and Linux builds I’ll hopefully also be working on some bug fixes and performance issues.

If this current beta build comes back clean after a few weeks (or after a few fixes), I’ll push it out as an official build.

Once the Mac and Linux builds are ready there will be a Beta test for them as well. For modding, the only thing that may change is that you may have to recompile mods to get new audio to work on other platforms – but otherwise existing mods should load on other platforms. I know there’s a few requests for a few other changes to the mod kit, and I’ll be looking into some of them to determine how easy (or difficult) the more desired ones are.


Back to Development!

Hrm, I haven’t gone this long without an update in a while… After working on the same project for a few years now, I found I needed a break doing something else for a while as well as a little time away from the screen. It’s probably good for me.

I’ve also been working on a lot of different things, which certainly slows down visible progress down when there’s only one developer working. I’m still working on the ports, but also a few fixes for the mod kit, and a few gameplay bugs, game dev tools, some brand new code for future projects, various experiments, and new game designs and prototypes.

I’m planning on doing an beta patch/update to Banished in the next couple weeks, which should include the following:

  • – The game (and modkit) will properly use the Textile limit, which should make some mods easier to play once updated. It also allows configuring places like farms, foresters, and orchards a type of resource it creates, so a mod could have a farm that grew cotton that obeys the textile limit.
  • – The game will use my SRSL language for materials, so any future ports to other platforms will hopefully just work graphics-wise.
  • – Lots of internal changes to the tech that shouldn’t actually change anything but need beta testing. Most of these are things that were changed to enable other platforms, such as using UTF8 for text, rendering changes to support OpenGL and other graphics APIs, and versioning code to load old save games and mods, which have changed format slightly.
  • – Various bug fixes that I’ll detail later when I release a beta patch.

I’m doing internal testing of all these changes and playing the game a bit to make sure I didn’t break anything too obvious. Once I’m satisfied I haven’t done anything too bad, I’ll release a beta patch for modders and anyone that wants to test the new code.



I’ve finally got OpenGL rendering Banished with identical output to both the DX9 and DX11 renderers. I’ve still got a little bit of work to support the boring parts of a rendering engine, such as handling window resizes, switches to fullscreen, and handling renderer changes at runtime. But all that’s platform specific code. What really matters here is I now have a working GL implementation that should (hopefully) seamlessly work on OSX and Linux.

Here’s a screenshot – which is pretty boring, except it’s rendered with OpenGL and isn’t missing any features.

Initial tests show that the OpenGL renderer is currently not as fast as DX11, but faster than DX9. Not having even tried an optimization pass yet, I’m fairly happy with the level of performance so far.

Getting OpenGL to work has been a bit of a learning experience. I hadn’t used GL since around 2003 – and obviously it’s changed a lot from 1.1 to 4.5. At least I no longer have to use a ton of extensions (but still a few!).

To support a large variety of systems, I’m limiting the renderer to using version 3.2. I like some of the API features in OpenGL/GLSL 4 better, but 3.2 supports everything Banished needs, and potentially supports everything I’d need for future games in the next few years.

Now that I do have OpenGL working, I can’t say that it’s any better or worse than Direct3D 9 or 11 (not that I didn’t do a bit of yelling and face palming while getting the implementation working). I don’t like the API as much as some others, but I’ve got a lot of years of bias on graphics APIs.

In the end it’s still just getting triangles on the screen mapped with textures with some fancy shader programs running. Anytime I’ve used a new system it’s required a new platform specific renderer – Playstation 2, 3, GameCube, Wii, Xboxs, etc all use different APIs, and in the end you get the same picture on the screen at the end of the day. Using GL is really no different. When Vulkan, Mantle, and DX 12 are more widely used, I’ll be doing this all again, but probably with different games.


Shading Languages

So for better or worse, I’ve designed my own shading language, written a compiler that parses it, it gives syntax and semantic errors, and if there are no errros, outputs the program in several other shading languages. This way, I can write a shader once and target multiple platforms. And plus, now I own the Dragon Book!

In line with GLSL, HLSL, PSSL, and the others, I’m calling my language SRSL for Shining Rock Shading Language. I’m seriously debating calling it SRSLY, because that’s funny, and owls are cool. I just need to decide what the Y stands for. Or not.

Defining the entire grammer and all the extensive language rules is probably beyond the scope of this post, so I’ll just be giving an overview here and lots of examples.

First some basics:

The body text of the shaders is very C like and should be understood easily coming from other shading languages. SRSL has all the intrinsic functions you’d expect from HLSL or GLSL. dot, cross, reflect, clamp, sin, cos, step, lerp, saturate, etc.

You’ll notice that all types in the examples are HLSL style – float, float2, float3, float4, int, int2, uint, uint2, float4x4, etc. I prefer this over GL style vec, ivec, uvec, and mat4. I think the HLSL style conveys more and better information about the built in types.

While none of these examples shows it, while loops, do loops, for loops, and conditionals are available in the language. However switch statements are not implemented.

One difference from C like languages is for the casting of variables between types. First, there is no automatic casting between int types and float types, as HLSL has. Also, if you had an int4 and wanted to cast it to float4, The C style cast would be:


But SRSL uses a more left to right readable post-fix op:


Another difference is declaration of arrays. In SRSL, arrays are defined with the array size as part of the type definition, like so:

// two arrays of matrix
float4x4[64] transforms, bones;

Whereas the C style declaration would be:

float4x4 transforms[64], bones[64];

The language has no global variables. Everything is passed to the shader entry point and if a function needs it, it has to be passed as a parameter. I’m pretty sure I’ve rarely or never used globals in shader programming so I didn’t see an immediate need for the language to have them. (Obviously in other languages vertex attributes, uniforms and textures can be globals, but that’s not what I’m talking about here.)

The only use case I can think of having globals are constants you want to define once, such as pi, or magic numbers used in more than one place. But rather than add another language feature, functions are used to return constant values. Due to shader compilers aggressive inlining, the call will always go away and just be a constant in the assembly.

For example:

float pi() { return 3.14159265359; }

Instead of something like:

const float pi = 3.14159265359;

So now I’ll get right to it, let’s take a look at a shader. Here’s the body of the simplest shader used in Banished.

program Debug
	stream(DebugVertex) uniforms(VSConstants) 
	vertexshader vs
		float3 worldpos = tc.transforms[0] * float4(input.position, 1.0);
		output.position = gc.worldToProjection * float4(worldpos, 1.0);
		output.color = input.color;
	pixelshader ps
		output.color = input.color;

My first goal for the language was to treat the vertex shader, geometry shader, pixel shader, etc as a single program – they’re usually written as pairs and triplets (or however many stages are in use), so the language should treat them as such. It should be hard to write a valid program where the parts of the pipeline (vertex, geometry, pixel) aren’t sync’d by default, however writing a lone pipeline stage should be possible if needed.

The way the language sees the graphics pipeline is stream of data, followed by some code, which outputs a stream of data, and then more code runs, and another stream of data is output.

If I ever add compute style shaders to the language, it probably won’t follow this paradigm – as compute shaders aren’t necessarily going to be used to put pixels on the screen. But since it’s my own language, I can always add specific syntax for whatever I need.

Anyhow, streams of data are defined by a struct. Each member of the struct can have an attribute that binds it to a specific hardware resource. Attributes aren’t required, except in a few cases – such as screen space position, instance index, vertex index, depth output, etc. All the : 0, : 1, : 2, assignments below are optional and the compiler will assign them if they aren’t specified.

// debug vertex describes the input data from the host program
struct DebugVertex
	float3 position : 0;  // bound to vertex attribute 0
	float4 color : 1;     // bound to vertex attribute 1

// The interpolant passes data from stage to stage
struct Interpolant
	float4 position : clipposition;  // special attribute for vs outputs
	float4 color : 0;  // interpolated attribute

// output color to render target. For multiple render targets, 
// multiple outputs can be defined, as well as depth output.
struct PixelOutput
	float4 color : 0;  // single color output

program Debug
	stream(DebugVertex) // stream definition (vertex attributes)
	vertexshader vs { ... }
	stream(Interpolant) // stream definition (passed from vs to ps)
	pixelshader ps { ... }
	stream(PixelOutput) // stream definition (output pixel colors)

For any shader, the stream defined above it is assigned to an automatically defined variable named ‘input’, and the stream after it is defined as ‘output’.

Often times, you want multiple pixel shaders per vertex shader, or vice versa. In that case you can define multiple shaders between streams. I’d use this for alpha test, different texture blending, skinning or morphing vertices, etc. As long as the streams are the same for shaders you can write something like this:

program Debug
	pixelshader ps1 { ... }
	pixelshader ps2 { ... }
	pixelshader ps3 { ... }

This also shows how you can write just a pixel shader, without the vertex shader preceding it.

Another design goal I had was to remove repetitive code in the shaders. This tends to happen quite a bit when you have a lot of similar shaders with small differences. An extra rim lighting highlight, or skinning a model. These are the same as a base shader with only a few lines added.

So the language allows you to insert shader code in a previously defined shader. In the next example, the shader ‘psalpha’ derives from ‘ps’ – all the code from the body is used, and then the clip instruction is appended on the bottom. This is a very common operation when defining some shader where a version is needed that discards pixels based on the alpha channel.

program Normal
	stream(Interpolant) uniforms(PSConstants) textures(OpaqueTexture)
	pixelshader ps
		float shadow = GetShadowValue(shadowMap, input.shadowProjection, pc.texelSize.x);
		float3 ao = sample(aoMap, input.texcoord.zw).xxx;
		float4 color = sample(diffuseMap, input.texcoord.xy);
		output.color.xyz = ComputeLighting(input.lightfog, color.xyz, shadow, 
                        float3(1.0, 1.0, 1.0), ao, pc.lightColor, pc.ambientColor, pc.fogColor);
		output.color.w = 0.0;
	pixelshader psalpha : ps
                // discard pixels when diffuse alpha is less than threshold
		clip(color.w - pc.alphaRef);

Not only can you append code to the end of a shader, but you can insert it somewhere in the middle using a label.

Below is a shader that computes the position of a vertex for use in shadow mapping. Note the label keyword. At that location, any vertex can be modified in local space if code is inserted there.

program Depth
	stream(ModelDepthVertex) uniforms(VSConstants) 
	vertexshader vs
		// get transform from list of instances
		float4x4 localToWorld = tc.transforms[input.instance];
		// decompress position from fixed point to float
		float3 position = input.position.xyz.cast(float3) * (1.0 / 512.0);
		label positionmod; // insertion point for vertex modification
		// apply local scale
		position *= localToWorld.row3.xyz;

		// transform to world, then to screenspace
		float3 worldPosition = localToWorld.cast(float3x4) * float4(position, 1.0);
		output.position = gc.worldToProjection * float4(worldPosition, 1.0);

When a skinned model needs to be rendered into the shadow map, just the skinning of the vertex can be inserted at the label positionmod. Note that the stream input for this shader is different, but as long as it contains all the inputs from the parent shader, it will compile just fine.

program DepthSkin
	stream(ModelDepthSkinVertex) uniforms(VSConstants) 
	vertexshader vs : Depth.vs(positionmod)
		position = SkinPosition(position, input.index, input.weight, bc);

At this point, you may be wondering about this fancy code insertion language feature, and why I’m not just using macros or functions to do the same thing.

With functions, each shader would have a long list of function calls, with many parameters, and many declarations for out parameters that are used by different parts of the shader. In my experience shaders are very volatile during development – they change all the time as features get added and removed, or new ideas are tested. Function signatures change frequently. If a function signature changes, I’d rather not spend the time to change 50 or 100 shaders to update the calling parameters. It’s easier to just have all the code inline and allow variables from one shader to be accessed without issue in another.

At least, that’s the idea – It’s worked well for reducing code size for Banished, and hopefully will do so for future projects as well.

Macros are something I’m not interested in implementing in the language, however there’s a simple preprocessor in the languages tokenizer, with simple #if, #ifn, #define, #else, #end, and #include. It allows for different compilation based on target and features, and for sharing of common functions and structs.

You might see something like this in the shader, to disable computation of shadow mapping coordinates at the lowest shader detail levels.

#ifn DETAIL0
// only include shadow computation when detail level isn't 0
output.shadowProjection = lc.shadowProjection[0] * float4(worldPosition, 1.0);

There is no requirement for preprocessor like tokens to be the first item on a line, so you might also see something like this with a conditional compile inline. DirectX 9 has no instance input as a shader variable so it has to be faked somehow. In Banished, it’s currently done like this:

// get transform from list of instances
float4x4 localToWorld = tc.transforms[#if DX9 input.position.w #else input.instance #end];

Functions can be defined outside of a program block for shared functionality, and look like typical C style functions:

float3 SkinPosition(float3 position, int4 index, float4 weight, BoneConstants bc)
	return ((bc.transforms[index.x] * float4(position, 1.0) * weight.x) +
		(bc.transforms[index.y] * float4(position, 1.0) * weight.y) +
		(bc.transforms[index.z] * float4(position, 1.0) * weight.z) +
		(bc.transforms[index.w] * float4(position, 1.0) * weight.w)).xyz;

Shaders use more than just vertex inputs – there are also uniform constants and textures that need to be passed to the shader. In designing the language, I wanted the use of constants and textures and their bindings to registers to look exactly like binding stream variables to hardware registers.

If you look back at the first example I presented, the vertex shader uses several constants, namely tc and gc. These are defined like this:

// vertex constants that can be accessed anytime and don't change per render
struct GlobalConstants
	float4x4 worldToProjection;  // used to transform to screenspace
	float4x4 worldToCamera;      // used to transform to cameraspace
	float4 cameraPosition;       // camera location
	float4 time;                 // current time in seconds
	float4 fog;                  // values for computing linear fog
	float4 fogColor;             // fog color

// list of instance transforms, changes per draw
struct TransformConstants
	float4x4[128] transforms;

// VSConstants is a list of all constant buffers available to the shader. 
// If used as constants input, this struct can only contain fields of other
// user defined structs
struct VSConstants
	GlobalConstants gc : 0;   // bound to constant buffer 0
	TransformConstants tc : 3;  // bound to constant buffer 3

When you want to use a set of vertex constants in a shader program it’s referenced like this:

stream(Vertex) uniforms(VSConstants)
vertexshader vs { ... }
stream(Interpolant) uniforms(PSConstants)
pixelshader ps { ... }

The idea here is that there’s no need for a whole lot of loose global uniform constants (or constant buffers) like in HLSL and GLSL. The host program only provides certain constants, and they are generally known to the shader program and are available all the time. This way they are explicitly defined, and once setup it’s hard to make a mistake, such as using a uniform constant meant for a pixel shader in a vertex shader.

For instances where constants are different, say for drawing a specific type of geometry, a different set of constants could be specified making sure that only the available constants are actually used by the shader.

Textures are defined in a similar manner. The texture struct can only contain texture types.

struct PSTextures
	texture2d diffuse : 0;  // here the attribute defines which index
	texture2d snow : 1;     // the texture / sampler is bound to.
	texture2d ao : 3;
	shadow2d shadow : 5;

stream(Interpolant) uniforms(PSConstants) textures(PSTextures)
pixelshader ps 
        float4 color = sample(diffuse, input.texcoord);
        color *= sampleproj(shadow, input.shadowProjection); 

I’m going digress a little bit here, and you’ll see some of my thought process when designing this language. The language is still new and may need some tweaking – this is one of those places.

If you’ve been close paying attention to any of the examples, you’ll notice a glaring inconstancy with uniforms and textures versus the shader inputs and outputs. Shader inputs and outputs are automatically defined variables of the input and output type – input.position, input.texcoord, output.position, output.color, etc.

Textures and uniforms are currently used without a name and the variables inside the struct are simply declared as locals to the shader. This is okay. But I’ve been trying to decide if I should make this consistent with the other shader inputs.

Currently uniforms and textures would be accessed such as:

float4 position = gc.worldToProjection * input.position;
float4 color = sample(diffuse, input.texcoord);

But I’ve been thinking about changing it to

float4 position = uniform.gc.worldToProjection * input.position;
float4 color = sample(textures.diffuse, input.texcoord);

I like this change for a few reasons. First, it’s consistent the with the way streams are handled, and second, it stops you from inadvertently polluting the local variable namespace with unintended names that you might otherwise use. One day I might add a new texture to a struct, and it’s name clashes with an existing local in a shader – requiring a name change to one item or the other.

On the flipside, streams could have the input. and output. dropped as well, but too often I want to put the same names in both structs (position, texcoord, color, etc) so prefixing them with input. and output. is better in my opinion.

In the case of textures, I might want a texture named diffuse and a variable named diffuse to represent the resulting color when the textures is sampled.

float4 diffuse = sample(textures.diffuse, input.texcoord);

That’s nice and fairly clear as to what the variable holds.

The real downside here is for uniforms. Having to write something like ‘uniform.gc.worldToProjection‘ all over the place may be overly verbose, however it’s absolutely clear what’s going on. I can think of a few ways to reduce the length, such as allowing a user specified name when declaring uniforms and textures such as…

stream(Interpolant) uniforms(PSConstants, u) textures(ModelTextures, t)
float4 position = u.gc.worldToProjection * input.position;
float4 color = sample(t.diffuse, input.texcoord);

On the other had, I could scope the textures with a variable and leave uniforms alone. Really this is just sugar on the language. It works fine as is, and I’ll probably make a decision one way or the other the more I use it.

Changing Banished to use the new language (once the compiler was written and debugged) has been fairly painless and the reduction in code redundancy is very good. (I’ve actually found several bugs in the original shader code by doing the conversion, Whoops!)

Banished has also been a good test bed for a variety of shaders – I think it would be hard to design something like this without a real world test case.

Everything is pretty much done, but I’m sure the compiler still has bugs in it that i’ll find as I write more shaders. There are also missing features I’d like to add at some point. Depending on what else I’m working on I may not add them until I need them.

There’s currently no texture array type yet, and there aren’t sampling functions to specify which mip to use. (but a new texture type and sampling function are fairly easy to add). There are no multidimensional arrays, but I can’t think of the last time I even used one in C++. Geometry shader support isn’t finished. And there’s no tessellation shader as of yet.

Phew. Don’t fool yourself, compilers and languages are big projects.

So that’s SRSL (or SRSLY…) in a nutshell. It works, I can draw stuff using it, it’s cross platform ready. Now I can finally finish the OpenGL graphics renderer. Woot.