A Shader Trick

(I was sending an email today about a simple trick we use in the shader system, and it seemed easy and useful to share that email publicly, so, here you go!)

There's one other place where fog snaps right now, and that is in time -- it is probably pretty easy to fix, though the explanation might be a little unexpected so I am Ccing it to the team as a general knowledge transfer thing.

In general, with floating point numbers, there is a problem with loss of precision when you do computations involving large numbers added to small numbers; the small numbers tend to get dropped and go toward zero. This is a general thing about computers, not just graphics (it is the price we pay for being able to pretend we can store real numbers in small amounts of memory). Most of the time it's not an issue and programmers don't think about it, but there are some times when it can be an issue.

In shaders, we often want to have a time variable, controlled by the CPU, that we can use to help generate effects. That time variable counts upward, maybe from the time the level started, or the time the game started. If that number is measured in seconds, then after 10 minutes it will be around 600, after 2 hours it will be 7200, etc. If someone leaves the game running for 3 days (a certification requirement on consoles!), it would get up over 250,000.

Those numbers don't seem super big, but at the same time, we also deal with numbers that are small. If the game is running at 60fps (which on PC these days is considered maybe a slow frame rate!), that's 0.017 seconds per frame, and we might be doing math that involves both this dt and the current time. At which point the ratio of the current time to the frame time is like 250,000 / (1/60) = 15 million. That is really very high and a recipe for all kinds of numerical problems you don't expect -- if you add the delta time to the current time, in a 32-bit float, the delta time is almost completely destroyed because there is not enough precision in the number. Long before this time, you'd start to see jitteriness in animation, things not matching up to where you'd expect them to be, etc.

One way to solve this is not to let the current time grow indefinitely -- at some point, you reset it to 0. You could pick a fixed amount of time, like I don't know, every minute, and at the end of the minute the time is back at 0 and you keep going. But if you want your effects to be seamless, they all have to match up exactly at the 1-minute boundary, which makes tuning things difficult and annoying. For example, if you want to make a cosine wave that is seamless across time, you can make the wave with a period of 1 minute, or half a minute, or 1/3 a minute, etc, and firstly this is annoying because when designers want to tune stuff, they have to know this, and it's unwieldy and confusing, and secondly, these numbers cannot be represented exactly in floating-point numbers either, so you're adding imprecision that way.

But there is an old graphics programmer trick that solves this very cleanly, that I think has been around for a long time (I first heard of it from Ignacio when we were working on The Witness).

For our cosine wave example mentioned above, we are going to be evaluating some function like cos(freq*time*2*PI), where 'time' is just the wall clock time according to the game, and 'freq' is some parameter controlling the frequency of the cosine wave, that we want it to be very easy for designers to adjust without introducing problems.

The constraint you have to meet, for your cosine waves to match up when the timer resets, is that they all have to evaluate to 1 at whatever the max time is -- because when you reset to time = 0, you will get cos(freq*0*2*PI), which is 1. So cos(freq*time_max*2*PI) also has to be 1, in other words, the cosine has to have gone around the circle an integer number of times. Since we have a factor of 2*PI in there, this means that our wave will be flawless any time freq*time_max is an integer.

How do we ensure that, easily, in a way that people don't have to think about? Pick, for time_max, a number of seconds that is 10 to some integer power -- for example, 1000. Then, any number you type into a tweak file, that has no more than 3 digits after the decimal, will result in an integer number of times around the circle. For example, if freq = 9.876, we get cos(9.876*1000*2*PI) == cos(9876*2*PI) == 1. This works for any number you pick with 3 digits after the decimal. If you need 4 digits, make time_max 10000, and so forth. If we are reading values from a tweak file, we can round them to the nearest 3 digits before sending them to the shader (and maybe output a warning, that the digits aren't being used, if the rounded number is appreciably different from the input number, to remind people of this system).

This works with any periodic function, we just used cosine here as an example. For linear shifts, like scrolling uv coordinates or whatnot, it's similar -- if you have uv coordinates from 0 to 1, you just need to make sure shift_rate*time_max is an integer in order for the scrolling to be perfect when the time resets. Similarly for the time parameter used to index a flipbook animation.

So the reason the fog is snapping after you play for a while is that the time sent to the shader system gets reset to 0 every 1000 seconds but the fog doesn't know about this. And the reason for the resetting is to make sure we maintain good precision in the time-based effects in the shaders.

Congratulations to the grant recipients!

We are pleased to announce that the grant recipients have been selected!

Congratulations to the following creators:

Anastasia Kuznetsova - Raison
AWK - Harmon:i:c
Dario Zubović - QBCO
29Games - RocketPuzzle
Asriele Aestas - Our Sky
Cyathea Tree Team - Out of Bounds Exception
David Rhee - Sticky Blocks
Nico Saraintaris - Trash Diamonds
Weird Sisters Interactive - It's a Zoo.
Ruby - SokoBunny
Skalmantas Šimėnas - Magnecopter
Andrius Mitkus - Rimtis (working title)
Jose Hernandez - Efficient Necromancy

We are excited to see how these games progress and we're looking forward to sharing updates.

Testing the Jai Compiler

As the company grows with projects beyond The Witness, we thought it'd be nice to show progress on something not too secretive. For those folks into technical posts, I'll discuss how we used our new programming language to create an automated framework to test the language itself.

---

Jon wanted to convert his demos into proper language tests, and add in new ones. Testing becomes increasingly important as we approach a closed-group release. As it turns out, the literature on compiler testing is scarce, and getting access to quality test suites is pricey (see C or FORTRAN). I've also disliked test frameworks in general, which added to the problem.

In the interest of feeling joyful and productive, I decided to sit and think about the specific reasons why I've been unhappy with frameworks, and to use the language to write one I'd enjoy using.

How I Like to Test

I like to ask personally interesting questions which the tests ought to help me answer. Often, they relate to the behavior of software at the threshold of cross-cutting concerns. For the jai compiler, they could be questions such as:

  • Can we make a procedure's return type randomly change at compile-time?
  • What if a polymorphic procedure is used as a constructor?
  • How do we prevent certain structs from using a procedure as a constructor?
  • Can we generate bytecode for a slightly different procedure because of the struct that is using it as a constructor?

Note: We tend to use the term procedure instead of function.

Don't worry if the questions seem bizarre (the language is new and hasn't been released yet). The common theme is they all require very different language features to interact with each other (i.e. cross-cutting). For example, the first item could be handled polymorphically with the modify directive, enums, a switch statement, and some random bit generator. The answer to the second item is that nothing bad happens; in fact, this opens the door for multiple structs to use the same polymorphic procedure as their constructor. This observation naturally leads to questions three and four. When I wrote the tests for some of these, compiler bugs were uncovered naturally and without much effort.

Note how these questions are answered by just writing programs. They are the language tests. Anything not helping me towards that feels like noise, and that explains why I haven't been happy with many testing systems. Here's the criteria I want a framework to meet to feel good about writing tests:

  1. Ability to write tests as if they were real, independent programs.
  2. Simple asserts the tests can use.
  3. By default, virtually zero interaction with the test framework.
  4. Minimal test suite reports that are nice to look at.

By a minimal report I mean showing me which programs ran and when, how many test asserts were called and where to find them if they failed. The report should look good so I can help my brain enjoy the overall experience.

Our Framework

To showcase the framework we ended up creating, let's go through an example, transforming a program into a language test.

Here's the program that tackles one of the simpler inquiries from earlier: What if a polymorphic procedure is used as a constructor? While we're at it we'll ask the same of destructors.

#import "Basic";

Thing :: struct {
    mem : *u8;
    value := 42;
    #constructor init_thing;
    #destructor free_thing;
}

init_thing :: (using thing : *$T) {
    mem = alloc(1000);
}

free_thing :: (using thing: *$T) {
    free(mem);
    mem = null;
    print("Thing memory freed.\n");
}

main :: () {
    {
        our_thing : Thing; // Constructor fires off.
        print("%\n", our_thing);
    } // Destructor fires off.
}

If we compile it, we see that it's valid:

 >>> jai poly_constructor.jai
 >>> poly_constructor.exe
 Thing { value = 42; mem = 1e4e6b4f790; }
 Thing memory freed.
 >>>

As I had mentioned, this means multiple structs can use the procedure as their respective constructors.

 #import "Basic";

Thing :: struct {
    mem : *u8;
    value := 42;
    #constructor init_thing;
    #destructor free_thing;
}

AnotherThing :: struct {
    mem : *u8;
    message := "I don't hold the meaning of life.";
    #constructor init_thing;
    #destructor free_thing;
}

init_thing :: (using thing : *$T) {
    mem = alloc(1000);
}

free_thing :: (using thing: *$T) {
    free(mem);
    mem = null;
    print("Thing memory freed.\n");
}

main :: () {
    //
    // Test with multiple structs.
    //
    {
        our_thing : Thing;
        print("%\n", our_thing);

        different_thing : AnotherThing;
        print("%\n", different_thing);
    }
}

Output:

 >>> jai poly_constructor.jai
 >>> poly_constructor.exe
 Thing { mem = 1d7df5bf790; value = 42; }
 AnotherThing { mem = 1d7df5c0190; message = "I don't hold the meaning of life."; }
 Thing memory freed.
 Thing memory freed.
 >>>

Great! And remember: this is just a program. We can explore the possibility space even more, define any number of procedures, declare hundreds of complex structs, import 3rd-party modules—you name it. However, let's pretend we're done answering our polymorphic constructor questions. To turn this into a language test, let's make two straightforward modifications (we'll add a couple of asserts while we're at it):

polymorphic_constructor :: () {
    //
    // Test multiple things.
    //
    {
        our_thing : Thing; // Constructor fires off.
        assert(our_thing.mem, "Thing didn't get an memory address.");

        different_thing : AnotherThing;
        assert(different_thing.mem, "AnotherThing didn't get an memory address.");
     }
 } @TestProcedure

Notice I changed the name from main to something else and added the simple note TestProcedure.
Notes are essentially comments recognized by the compiler that do nothing by default.

Let's put that file in a tests/ folder.

 >>> mv poly_constructor.jai tests/
 >>> ls
 tests/
 tests.jai
 >>>

Ah, the reader will notice I have a file conveniently called tests.jai. Let's compile it.

 >>> jai tests.jai
 >>> ls
 tests/
 tests.exe
 tests.jai
 >>>

That produced tests.exe; let's run it! We'll use some real screenshots this time.

Running tests.exe

And we just made a full test suite run.

Note: The reader might ask why we have 82 more asserts than what we wrote, plus another test procedure
from a String file. More on that soon.

Notice a log file was generated. Here's the summary portion of that report:

Summary portion of report for poly_constructor.jai

Report format is based off Aras Pranckevicius' API doc

That's a lot of useful output just by compiling a source file! In addition, we can have multiple test procedures on the same test file:

    polymorphic_constructor :: () {
        //
        // Test multiple things.
        //
        {
            our_thing : Thing; // Constructor fires off.
            assert(our_thing.mem != null, "Thing points to null.");

            different_thing : AnotherThing;
            assert(different_thing.mem != null, "AnotherThing points to null.");
        }
    } @TestProcedure

    polymorphic_constructor_uninit :: () {
        //
        // Control when the initializer and constructors
        // actually get called.
        //
        {
            our_thing : Thing = ---; // Uninitialized var.
            memset(*our_thing, 0, size_of(Thing));

            T :: type_of(our_thing);
            tis := cast(*Type_Info_Struct) type_info(Thing);
            initializer := cast((*T) -> void) tis.initializer;
            constructor := cast((*T) -> void) tis.constructor;

            initializer(*our_thing);

            assert(our_thing.value == 42, "Expected % but got %\n", 42, our_thing.value);
            assert(our_thing.mem == null, "Expected % but got %\n", null, our_thing.mem);

            constructor(*our_thing);

            assert(our_thing.value == 42, "Expected % but got %\n", 42, our_thing.value);
            assert(our_thing.mem != null, "Didn't want % but got % anyway", null, our_thing.mem);

            //
            // We can do the same for AnotherThing.
            //

            // ...
        }
} @TestProcedure

Running tests.exe

Opening the report we get:

New summary portion for poly_constructor.jai

If we wanted to, we could drop in more test files in the tests/ folder, each containing their own sub-programs. Finally, if invariances in our tests don't hold (i.e. an assert fails), that will also be reported. Let's add a deliberate failure on the first test procedure:

    polymorphic_constructor :: () {
        //
        // Test multiple things.
        //
        {
            our_thing : Thing; // Constructor fires off.
            assert(our_thing.mem != null, "Thing points to null.");

            //
            // Deliberate failure.
            //
            assert(our_thing.value == 999, "Meaning of life isn't 999? It's %", our_thing.value);

             different_thing : AnotherThing;
             assert(different_thing.mem != null, "AnotherThing points to null.");
         }
} @TestProcedure

Running tests.exe with a failed assert

And we get that on the log file too:

Summary portion of report notifying the failure

If the test writer heads to the test files section, the procedure that failed would be made apparent.

Library Tests

So how do we explain that weird String file from the reports earlier? We didn't write that! Well, Ignacio found it useful to use this framework for the language's system modules. System modules are special .jai files we provide programmers. Users can import them to leverage OpenGL / D3D, font rendering, audio playback, and more.

The test file from earlier imported Basic (a catch-all module we use to experiment new features). Basic
happens to indirectly import the String module. When I opened up String, I noticed Ignacio had written this somewhere on the file:

string_tests :: () {

    assert(join(..{:string: "foo", "bar", "puf"}, ", ") == "foo, bar, puf");
    assert(join("foo", "bar", "puf", separator="/") == "foo/bar/puf");

    assert("foo/bar/puf" == join(..split("foo/bar/puf", "/"), "/"));
    assert("foo\\bar\\puf" == join(..split("foo/bar/puf", "/"), "\\"));
    assert("foo/bar/puf/grr" == join(..split("foo, bar, puf, grr", ", "), "/"));

    // More asserts
    // ...
} @TestProcedure

Which accounted for the 82 additional asserts. That's because the framework detects tests written in external modules, outside of our own test files. Now anyone on the team writing modules can write test procedures inside their module, tag them, and they're done. In fact, I have a file under tests/ that just imports all the modules. The actual tests can live right next to the module code, outside of our folder, and we register them for free just as we automatically register the test files inside the folder for free. And fret not, a module being imported multiple times will still have its test recognized only once.

Future Updates

I'd like to note there are special settings and configurations the user doesn't know about (good! that's the point!), and those who care can open up the build file, which is just the tests.jai source file. Documentation amounts to a few well-placed comments.

Upon release (if we have time to make it happen), I'd like people to write their numerous test procedures, compile tests.jai, and have them witness Jon's bananas.jai but in the context of tests: A visualization of a growing tree of test files as branches, where each test procedure is a leaf from the branch.

How It All Works

A common theme back at Kennedy Space Center was that we wanted to automate most of the "bookkeeping" so the engineer's time and effort went into thinking about the real problem. Interacting with build systems was not in our best interest! Even so, sometimes we were stuck with what I think were high-friction tools. I can't properly express to the reader how great it's been to write a tool that reduces friction nontrivially.

Let's take a quick look at how the language played a part in letting me create this framework, as I think it has the right kind of features all statically-typed languages should have going forward.

Minimizing Framework Interaction

Initially, the main struggles in making the testing system work were psychological, working to unlearn years of how I was told automated test frameworks should be structured. My first attempt was writing a hybrid C++/Jai program with wacky command-line parameters. I think Jon nearly had a heart attack. It was difficult to implement something that met the criteria listed at the beginning of this post, because my model was tied to old-school software testing models.

The closest implementation I have ever found is CxxTest. What caught my interest was the second sentence on their homepage:

CxxTest is easy to use because it does not require precompiling a CxxTest testing library, it employs no advanced features of C++ (e.g. RTTI) and it supports a very flexible form of test discovery.

Aha! It's trying to make itself invisible, and the term test discovery sounds like it's doing work on behalf of the user. Indeed, later the website goes on to say:

Additionally, CxxTest supports test discovery. Tests are defined in C++ header files, which are parsed by CxxTest to automatically generate a test runner. Thus, CxxTest is somewhat easier to use than alternative C++ testing frameworks, since you do not need to register tests.

Aha por dos! Test discovery is the property I want frameworks to exhibit. With CxxTest, though, we still need to understand its build system steps to reap the benefits, but that's a difficult limitation to overcome with C++. In jai, however, we can specify our build system in the language.

So I made a source file, tests.jai. In said file, we can ask the compiler to compile any number of different files, and request it to open up its compilation stages to us as things get lexically scanned, parsed, and code generated. That's more than we care to ask for, so our entire build system can amount to compiling a single file, in the same way we compile any normal code.

Tagging Procedures

As I just said, in tests.jai we can ask the compiler to pause at any compilation stage for us to intervene in the process. So I used that for the test discovery. The current mechanism, as we have seen, is to tag procedures that want to behave like an independent test program with the note @TestProcedure. Then, once the compiler has finished type-checking all source files, I ask it to give me the declarations tagged with the note. Specifically:

for decl.notes {
    if it.text == {
        case "TestProcedure";
        proc_info := cast(*Type_Info_Procedure) type;
        if proc_info.argument_types.count > 0 {
            print("[Test Suite] WARNING: '%' tagged with @TestProcedure should take no arguments.\n", proc.name);
            continue;
        }
        user_test : TestProcedure;
        user_test.filename = decl.filename;
        user_test.procname = proc.name;
        array_add(*tests_to_register, user_test);

        //...
        case;
        // Do nothing.
    }
}

Where decl is the current code declaration we're inspecting from the AST.

In the past, I used to check for procedure names starting with test, but Allen and Nico made me question whether that was too constraining. Turns out it was. The downside with the notes here, though, is there might be tests importing external modules that tagged some of their code with @TestProcedure for unrelated reasons. I mitigate that by ignoring structs with that tag, and by verifying whether the procedure acts as an entry point (i.e. main with no parameters). There are other options available to us, but for now this is a good-enough solution.

Hidden Test Suite

I register the discovered tests by creating register_test procedure calls on-the-fly--meaning I update the program's AST before things enter the bytecode pipeline. Where is the register_test procedure defined? Well, the dirty secret is I still wrote an actual test suite, contained in a single file living inside the tests/ folder. The build file has access to that, as do people. If anyone wishes to avoid all the automatic magic and also believe editing the build file is not enough, they are free to ignore it altogether and figure out how the suite works. They can register tests manually, generate reports however they see fit, and perform the entire job of a framework themselves.

Test Asserts

As we can see from the CxxTest homepage, it has different types of asserts for us to use. I had something similar going, but Ignacio reminded me that Jon added an assertion handler as part of the implicit context. That means we can override the behavior of regular asserts! This is what allows external modules to write officially-recognized tests inside their file and still compile anywhere--we're not requiring them to do anything special.

What's Next

I now enjoy writing tests whenever I need to, and I hope this straightforward style sounds appealing to some people.

In other news, for the purpose of testing a compiler, will an automated test suite suffice? Not really, though it's a necessary first step. Although we've uncovered dozens upon dozens of important compiler bugs, we're starting to hit the ceiling of what human-made tests can offer. It's no surprise we've started to seriously look into a different beast entirely—a compiler fuzzer.

But that's a story for another blog post!

P.S. Shoutout to Josh, who among other things, helped write a bunch of the language tests and makes sure everything keeps running smoothly on Mac and Linux.

The Witness is finally on iOS!

In case you haven't heard the news, after nearly a year of optimizing, refining touch-controls, etc., we finally released The Witness on iOS!

You can find it on the App Store, and carry around the full game in your pocket...which is pretty crazy if you ask me.

The game will work with iOS 10 and up, on:
iPhone 5S (or newer)
iPad Air (or newer)
iPad Mini 2 (or newer)
iPod Touch 6th Generation

Oh, also, it's only $9.99!

MacOS System Requirements

We released The Witness for MacOS a little while back, and we felt it'd be a good idea to post the system requirements somewhere so we could link people to them when they ask. So...

The Witness requires Metal for rendering. Metal is a graphics technology from Apple that allows faster and more fluid graphics. These are the Mac computers that support Metal:

  • MacBook (Early 2015)
  • MacBook Air (Mid 2012 or newer)
  • MacBook Pro (Mid 2012 or newer)
  • Mac mini (Late 2012 or newer)
  • iMac (Late 2012 or newer)
  • Mac Pro (Late 2013 or newer)

https://support.apple.com/en-us/HT205073

It's possible to build custom Mac systems with Metal support, either using an old Mac Pro and upgrading the graphics card or building it from scratch using compatible parts (Hackintosh). These configurations are unsupported, but some customers have found them to work.

If using an AMD GPU, the only supported add-in card with Metal support is:

AMD Radeon 7950 (Tahiti Pro) Sapphire card.

If using an NVIDIA GPU, any GeForce 6xx or newer should work, but it is necessary to install the latest drivers from:

http://www.nvidia.com/object/mac-driver-archive.html

System Requirements:

Minimum:

* OS: El Capitan 10.11.6
* Processor: Intel Core i5 @ 1.8 GHz
* Memory: 4 GB RAM
* Graphics: 1 GB of video memory
- Intel: HD Graphics 4000
- AMD: Radeon R9 M290
- NVIDIA: GeForce 640M
* Storage: 5 GB available space

Recommended:

* OS: Sierra 10.12.4
* Processor: Intel Core i7 @ 2.4 GHz
* Memory: 8 GB RAM
* Graphics: 2 GB of video memory
- AMD: Radeon R9 M390
- NVIDIA: GeForce 780
* Storage: 5 GB available space

The list of Apple systems with AMD graphics that support Metal is:

Mac Pro 6,1 (Radeon FirePro D300, D500, D700) - cylinder
iMac 15,1 (Radeon R9 M290, M295X)
iMac 17,1 (Radeon R9 M380, M390, M395, M395X)

MacBook Pro 11,5 (Radeon R9 M370X)
MacBook Pro 13,3 (Radeon Pro 460)

The list of Apple systems with NVIDIA graphics that support Metal is:

MacBook Pro 9,1 (GeForce GT 650M)
MacBook Pro 10,1 (GeForce GT 650M)
MacBook Pro 11,3 (GeForce GT 750M)
iMac 13,1 (GeForce GT 640M, 650M)
iMac 13,2 (GeForce GTX 660M, 675M)
iMac 14,2 (GeForce GT 755M, 775M)
iMac 14,3 (GeForce GT 750M)

Help Us Get The Witness on iOS!



If you are looking for 2-3 months of straightforward contract work, are excellent at optimizing art assets in Maya, can pick up workflows quickly and have good attention to detail, then we are looking for you! We are planning on shipping The Witness on iOS in a few months, but still have a lot of optimization work to get done.


Familiarity with The Witness is obviously a plus, as it is a very intricate game with a lot of gameplay-relevant details. Being able to work in-office in our San Francisco studio would also be nice, but is not required if you are really good at communicating long distance.


Please only apply if you have shipped games before and have a good idea of the optimiziation process, we do not have time to bring someone inexperienced into the fold!


Send resumes/portfolios to [email protected]

Anniversary!

The Witness was first released a year ago.

To celebrate, the game is on sale for 50% off on Steam, here: http://store.steampowered.com/app/210970/

Braid is also on sale, and if you already own it, you get an extra discount on The Witness: http://store.steampowered.com/bundle/2501/

Meanwhile, we are working on The Witness for MacOS, and it should be done pretty soon. iOS is also coming up but is a bit further in the future.

Happy 2017!

PS4 Pro patch update

Previously, I made a post listing the technical details of the PS4 patch.

That was a work-in-progress, and I said at the time I'd post final specs as soon as we know them, which we now do.

We are definitely supporting HDR, on both the PS4 Pro and the base model PS4. If the game detects an HDR TV, it will start in HDR mode. HDR works in all resolutions mentioned below.

If you have a PS4 Pro, and are outputting to a 1080p TV, The Witness will render at 1080p, 60fps, 4x MSAA (which is substantially higher than the base PS4's 900p, 60fps, 2x MSAA). This is the same as we announced in the earlier posting.

If you have a PS4 Pro, and are outputting to a 4k TV, then you get a choice of two modes from the options menu:
(a) 1440p 2x MSAA rendering of the game world, upscaled to 4k, text and UI rendered at 4k, 60fps
(b) 4k 2x MSAA rendering for everything, 30fps 

We added option (b) because a lot of people seem to want it, and it does look really good!

I don't yet know the exact release date for the patch, but it is coming pretty soon, in November sometime. We are now in the process of nailing down all the little details and making sure they are good. As soon as we know the release date I will announce it here.

 

The Witness is out on the Xbox One!

I should have posted this several days ago, but we have been busy with launch stuff!

If you have an Xbox One, you can play The Witness there! Unless you are in Australia or New Zealand ... in which case we need to get the ratings stuff pushed through. We are working on that.