The Factor UI framework is a tree of gadgets — labels, buttons, tracks, tables, panes, and others. While we have pretty good documentation in some places, sometimes it can be a little sparse in others. For learning, nothing beats seeing how a gadget actually looks and what the code that generates it looks like.
Recently, I wrote a ui-demo vocabulary. At the moment, it contains one section per gadget type. You can click around and see some examples.
Each section right now is one small word. For example, here is the whole packs section shown above:
: <three-boxes> ( pack -- pack )
{ 3 3 } >>gap
COLOR: DodgerBlue { 60 25 } <color-box> add-gadget
COLOR: MediumSeaGreen { 90 25 } <color-box> add-gadget
COLOR: chocolate1 { 45 25 } <color-box> add-gadget ;
: <packs-section> ( -- gadget )
<section>
"A pack lays its children out along one axis, each at its preferred size." <prose> add-gadget
"Shelf, horizontal:" <heading> add-gadget
<shelf> <three-boxes> add-gadget
"Pile, vertical:" <heading> add-gadget
<pile> <three-boxes> add-gadget
"Filled pile, children stretched across:" <heading> add-gadget
<filled-pile> <three-boxes> add-gadget
<page> ;
You can check it out from within a Factor listener:
IN: scratchpad "ui-demo" run
Or try ./factor -run=ui-demo from the command line.
I am perhaps susceptible to nerd-sniping and it often leads me down interesting rabbit holes. Today was one of those days. I bumped into a fun article about some obfuscated bash code on a T-shirt for sale:
The obfuscated code in question is actually an easter egg, it’s being supplied via Uniqlo stores on an excellent t-shirt designed by Akamai in support of their Peace for All campaign.
While reading about the author’s use of
OCR to
convert the printed text into a string that they could compute base64 --decode on to see the resulting program, I had major nostalgia for
carefully typing programs from computer
magazines so they could be
run locally – the only option for us kids at the time.
Raylib can be a great tool for doing colorful animations and is pretty well supported by Factor. I thought it would be fun to show how to write a similar program using it!
First, we define some constants for our message (infinitely looping using a circular sequence), font sizes, text colors, and then a computed number of visible text rows:
CONSTANT: message $[ "♥PEACE♥FOR♥ALL" <circular> ]
CONSTANT: width 800
CONSTANT: height 600
CONSTANT: font-size 24
CONSTANT: freq 0.2
! colors move from cyan to orange
CONSTANT: color-start S{ Color f 0 255 255 255 }
CONSTANT: color-end S{ Color f 255 135 0 255 }
: rows ( -- n ) height font-size /i ;
The default font doesn’t include a heart glyph, so we need to be able to manually draw one:
:: draw-heart ( x y color -- )
font-size :> s
x s 0.30 * + >integer y s 0.32 * + >integer s 0.22 * color draw-circle
x s 0.70 * + >integer y s 0.32 * + >integer s 0.22 * color draw-circle
x s 0.50 * + y s 0.95 * + <Vector2>
x s 0.92 * + y s 0.42 * + <Vector2>
x s 0.08 * + y s 0.42 * + <Vector2>
color draw-triangle ;
Otherwise, we draw our character as a function of a “tick”, moving
horizontally in x according to a sine
wave, and changing colors within
our color range:
: wave-x ( tick -- x )
freq * sin width 4 /i * width 2 /i +
round >integer 0 width font-size - clamp ;
: wave-color ( tick -- color )
[ color-start color-end ] dip rows mod rows /f color-lerp ;
:: draw-glyph ( tick row -- )
tick wave-x :> x
row font-size * :> y
tick message nth :> ch
tick wave-color :> color
ch CHAR: ♥ = [
x y glyph color draw-heart
] [
ch 1string x y font-size color draw-text
] if ;
We can now define a rendering function that we use each tick, that draws a glyph on each row:
: render ( tick -- )
begin-drawing
BLACK clear-background
rows <iota> [ [ + ] keep draw-glyph ] with each
end-drawing ;
And then a simple loop where we open a window, and increment the tick and render each frame:
: open-peace-window ( -- )
width height "♥ PEACE FOR ALL ♥" init-window
30 set-target-fps ;
: peace-for-all ( -- )
open-peace-window 0
[ window-should-close ] [ [ 1 + ] [ render ] bi ] until
drop close-window ;
It looks pretty good!
The code for this is on my GitHub.
We have a contributor that has been working hard on support for native ARM64 compilation in Factor. Simultaneously, we are working on getting that into the C++ VM as well as the new Zig VM that might eventually replace it in a future version.
I wanted to give a preview now that the native ARM64 version seems to work pretty well on macOS. Now that we are native, we expect significantly improved performance. You can get a sense of that by looking at our “core bootstrap time”:
Using the Intel version with Rosetta2:
Core bootstrap completed in 9 minutes and 6 seconds.
Using the Apple silicon native version:
Core bootstrap completed in 2 minutes and 20 seconds.
It hasn’t yet passed the entire test suite, and is not available yet as a nightly build. But, if you feel adventurous, you can grab the latest development version and bootstrap your own Super Fast Native(tm) version:
$ zig version
0.16.0
$ zig build --release=fast
$ ./factor -i=boot.unix-arm.64.image
$ ./factor
Factor 0.102 arm.64 (2311, heads/master-e4bd6e56b3, Jun 1 2026 14:22:54)
[Zig 0.16.0 ReleaseFast] on macos
Give it a try!
I wanted to look at ATS again after a long gap. I've written about it many times before but haven't done much with it in recent years. Part of what prompted this was looking at Verus for verified Rust programming and thinking about how ATS compares. ATS takes a different approach to verification. It's built into the type system itself rather than added as an annotation layer. Proofs are first-class values that you construct and pass around. It compiles to C and the proofs are erased during compilation so the generated code is just plain C with no runtime overhead.
In this post I go through the steps I needed to build ATS1 and ATS2 from their GitHub repositories on Linux and macOS. I expected this to be straightforward but ended up spending a fair amount of time working through bitrot in the existing documentation and build system.
The last official ATS2 release was version 0.4.2 in 2020. Development has continued on GitHub but there hasn't been a new release tarball. The ATS website still links to older releases. Most of the build instructions I found online are out of date or refer to release tarballs that aren't the best way to get started anymore. Building from the GitHub repositories requires building ATS1 (ATS-Anairiats) first since the ATS2 compiler is written in ATS1. This is where most of the problems are. The ATS1 repository has broken autotools symlinks, missing directories and issues with newer versions of macOS. I spent some time working through these and documenting the fixes here.
On Linux (Debian/Ubuntu) the dependencies are straightforward:
$ sudo apt-get install -y build-essential automake bison libgmp-dev
On macOS with Homebrew there are a few extra steps. Install the dependencies:
$ brew install automake bison gmp gcc
Homebrew's bison needs to be added to the PATH because macOS ships an older version that is too old for the ATS1 build:
$ export PATH="/opt/homebrew/opt/bison/bin:$PATH"
This is for Apple Silicon. On Intel Macs the path is /usr/local/opt/bison/bin instead.
Both ATS1 and ATS2 need to be cloned:
$ mkdir -p ats && cd ats
$ git clone https://github.com/githwxi/ATS-Anairiats.git
$ git clone https://github.com/githwxi/ATS-Postiats.git
The ATS1 repository has some issues that need to be fixed before building. The first is that the install-sh and missing files are symlinks pointing to an automake-1.15 installation that won't exist on your system. These need to be replaced:
$ cd ATS-Anairiats
$ rm -f install-sh missing
$ automake --add-missing --copy 2>/dev/null || true
The automake command will print warnings about missing Makefile.am files but this can be ignored. It copies the real install-sh and missing scripts from your system's automake installation into the working directory, replacing the dead symlinks.
The build also expects a bootstrap1 directory that doesn't exist in the repository. I'm not sure if this is something that used to be generated by a step that no longer works or if it was always missing:
$ mkdir -p bootstrap1
With the symlinks and directory fixed, configure the build. For Linux:
$ ./configure
On macOS, Homebrew installs GMP to a non-standard location so the paths need to be passed to configure:
$ CPPFLAGS="-I$(brew --prefix gmp)/include" LDFLAGS="-L$(brew --prefix gmp)/lib" ./configure
GMP is needed because ATS2's type checker generates integer arithmetic constraints at compile time and the patsopt compiler uses GMP for arbitrary-precision constraint solving. There is also a non-GMP variant that uses machine integers instead but it can overflow on complex constraints so GMP is what should be preferred. The rest of this post assumes the GMP build.
Check that the output includes checking for gmp.h... yes. If it says no then the ATS1 libraries will be built without GMP support and the ATS2 build will fail later with confusing linker errors. This tripped me up on my first attempt on macOS.
Pre-existing archive files in the repository are checked in as read-only which causes errors during the build:
$ chmod u+w ccomp/lib/*.a ccomp/lib64/*.a 2>/dev/null
Set the required environment variables and build:
$ export ATSHOME=$PWD
$ export ATSHOMERELOC=ATS-0.2.12
On macOS there is an additional issue that took me a while to figure out. Apple Clang treats implicit function declarations as errors whereas GCC on Linux only warns. The ATS1 codebase triggers these in several places. There are two mechanisms that invoke gcc during the build and each needs its own fix:
$ export C_INCLUDE_PATH="$(brew --prefix gmp)/include"
$ export LIBRARY_PATH="$(brew --prefix gmp)/lib"
$ export CC="gcc -Wno-error=implicit-function-declaration"
The CC variable handles Makefile $(CC) calls. The ATS1 batch compiler atscc also invokes gcc internally via execvp() and passes the ATSCCOMP variable directly as the program name without shell word splitting. I initially tried just setting ATSCCOMP="gcc -Wno-error=implicit-function-declaration" but that doesn't work because atscc doesn't split it into separate arguments. I used a wrapper script intead. I'm not sure it's the best fix but got things working:
$ mkdir -p "$ATSHOME/bin"
$ cat > "$ATSHOME/bin/atscc-gcc" <<'EOF'
#!/bin/sh
exec gcc -Wno-error=implicit-function-declaration "$@"
EOF
$ chmod +x "$ATSHOME/bin/atscc-gcc"
$ export ATSCCOMP="$ATSHOME/bin/atscc-gcc"
With the environment set, the build is the same on both platforms:
$ make all0
The all0 target builds without GC support which is fine for only using it to build ATS2. The all target links against the Boehm garbage collector which the ATS runtime can use for automatic memory management. Since we only need ATS1 to compile ATS2's source to C, the garbage collector isn't needed. The build takes a couple of minutes and produces a lot of output as it compiles the ATS1 compiler and its standard library. Verify it worked:
$ bin/atsopt --version
ATS/Anairiats version 0.2.13 with Copyright (c) 2002-2014 Hongwei Xi
The version reports 0.2.13 which is confusing because ATSHOMERELOC needs to be set to ATS-0.2.12. These are actually unrelated. The version number is just what atsopt reports. ATSHOMERELOC controls how ATS encodes module paths into C symbol names. ATS uses percent encoding to turn paths like prelude/DATS/unsafe.dats into C symbols. Without ATSHOMERELOC it uses the full absolute path from your filesystem which produces symbols that don't match anything in libats.a. The value ATS-0.2.12 is what was used when we compiled those library archives in the make all0 step. It doesn't matter that the compiler itself is version 0.2.13.
With ATS1 built, set the environment variables for the ATS2 build. Run these from the parent ats/ directory:
$ export ATSHOME=$(pwd)/ATS-Anairiats
$ export ATSHOMERELOC=ATS-0.2.12
$ export PATSHOME=$(pwd)/ATS-Postiats
The ATSHOMERELOC variable is important and I spent some time debugging what happens without it. If it's not set, ATS1 generates C code with absolute filesystem paths baked into symbol names. These won't match the symbols in libats.a and you get confusing linker errors like:
undefined reference to `_2Users_2you_2ats_2ATS_2dAnairiats_2prelude_2DATS_2unsafe_2edats__staload'
On macOS keep all the environment variables from the ATS1 build step exported as the ATS2 build uses them too. The ATS2 build itself is a single make target:
$ cd ATS-Postiats
$ make -f Makefile_devl all
This takes a few minutes. It uses atsopt (ATS1) to compile the ATS2 compiler source to C, then gcc to compile that C into the patsopt binary, then patsopt to build patscc and the standard library. The Makefile_devl is used instead of the regular Makefile because the regular one expects a pre-built patsopt to already exist. The _devl variant bootstraps everything from ATS1.
Verify the build:
$ bin/patsopt --version
ATS/Postiats version 0.4.3 with Copyright (c) 2011-2021 Hongwei Xi
$ export PATH=$PATSHOME/bin:$PATH
$ cat > /tmp/hello.dats << 'EOF'
implement main0 () = println! ("Hello from ATS2!")
EOF
$ patscc -o /tmp/hello /tmp/hello.dats
$ /tmp/hello
Hello from ATS2!
The patscc wrapper handles calling patsopt to compile ATS to C and then invoking gcc to produce the binary. The -o flag works the same as with gcc. If you look at the generated C in /tmp/hello_dats.c you can see the output of the compilation. ATS generates readable C with comments indicating the source locations.
When building from the GitHub source there is no make install target so ATS2 just runs from the source directory. The patsopt compiler needs PATSHOME to find the prelude, runtime headers and standard library at compile time. Without it you get errors about missing SATS and HATS files when trying to compile anything that includes the prelude. I added these to my shell profile:
export PATSHOME=/path/to/ATS-Postiats
export PATH=$PATSHOME/bin:$PATH
On macOS also add the GMP path exports:
export C_INCLUDE_PATH="$(brew --prefix gmp)/include"
export LIBRARY_PATH="$(brew --prefix gmp)/lib"
The GitHub build has no make install target but it is possible to produce a self-contained release tarball that can be built with just a C compiler and no ATS1. The release infrastructure lives in ATS-Postiats/doc/DISTRIB/. I went through the process of generating one to see if it still works and to document the steps.
This requires autoconf in addition to the earlier prerequisites:
$ sudo apt-get install -y autoconf
All environment variables from the ATS1 and ATS2 build steps must still be set. The first step is to generate the CBOOT files which are the pre-generated C source that allows building without ATS1:
$ cd $PATSHOME
$ make -f Makefile_devl all
$ make -C src -f Makefile CBOOTgmp
The CBOOTgmp target takes the compiled ATS2 source and generates standalone C files that can bootstrap the compiler without needing ATS1. These go into src/CBOOT/.
The distribution template directory under doc/DISTRIB/ATS-Postiats needs the env.sh templates and a generated configure script:
$ cp $PATSHOME/bin/*_env.sh.in $PATSHOME/doc/DISTRIB/ATS-Postiats/bin/
$ cd $PATSHOME/doc/DISTRIB/ATS-Postiats
$ sh ./autogen.sh
$ ./configure
Generate C for the standard library:
$ cd $PATSHOME
$ make -C src/CBOOT/prelude -f Makefile
$ make -C src/CBOOT/libc -f Makefile
$ make -C src/CBOOT/libats -f Makefile
These targets use patsopt to compile the prelude and standard library ATS files to C, placing the results alongside the compiler CBOOT files so the release tarball is fully self-contained.
Generate the distribution Makefile from Makefile.atxt using atsdoc:
$ cd $PATSHOME/doc/DISTRIB
$ make -f Makefile.gen
With everything generated, the final packaging step assembles the tarball:
$ make -f Makefile atspackaging
$ make -f Makefile atspacktarzvcf_gmp
This produces ATS2-Postiats-gmp-0.4.3.tgz in the current directory. The tarball contains all the pre-generated C source, the configure script and Makefile so that end users can build with just a C compiler.
If you'd rather not run all of these steps manually, there is a build_release.sh script in share/SCRIPT/ that automates the process. I haven't tested it thoroughly but it should be run from a clean working directory outside the source tree:
$ cd /tmp
$ sh $PATSHOME/share/SCRIPT/build_release.sh 0.4.3 gmpknd
The release tarball only needs a C compiler to build. No ATS1 is required since the tarball includes the pre-generated C source for the compiler. Extract and build:
$ tar xzf ATS2-Postiats-gmp-0.4.3.tgz
$ cd ATS2-Postiats-gmp-0.4.3
$ ./configure
$ make
$ sudo make install
The configure script detects GMP and sets up the Makefile. The make step compiles patsopt from the CBOOT C source and then builds patscc and the standard library.
On macOS, GMP headers and libraries are in Homebrew's prefix rather than the default search paths, so pass them to configure:
$ CPPFLAGS="-I$(brew --prefix gmp)/include" LDFLAGS="-L$(brew --prefix gmp)/lib" ./configure
This installs to /usr/local by default. The installed wrapper scripts set PATSHOME automatically so no manual environment setup is needed after install. To install to a different location use --prefix:
$ ./configure --prefix=$HOME/.local
$ make
$ make install
After make install you should be able to run patscc directly to compile ATS programs.
Getting ATS built from source in 2026 requires working around some bitrot in the build system but it is doable on both Linux and macOS. The main pain points for me were the broken autotools symlinks in the ATS1 repository, macOS Clang treating implicit function declarations as errors and getting GMP paths right on macOS.
Some further reading:
It is possible that the next Factor release will be re-implemented in the Zig programming language and be faster in many cases compared to the current C++ VM.
As a reminder, we have had several implementation eras, using:
Over the last few years, I’ve gained some experience using Zig. First,
learning some basics implementing SMAC in Factor and
then excitedly realizing that Factor is faster than
Zig. I later wrote the Zen of
Factor inspired partly by zig zen. And
recently, spent some time benchmarking Factor against Zig running One
Billion Loops.
It would be reasonable to ask if I’ve gotten Zig-pilled yet.
Maybe I’m now just getting ziggy with it.
We are living in a golden age with many great programming languages and dedicated communities building excellent options to choose from across a variety of different dimensions. For example – and this is by no means an exhaustive or necessarily correct list – you could choose to prioritize:
There are some features that I particularly appreciate about Zig:
Over the last few months, I have been working on a mostly apples-to-apples port of our existing C++ VM to Zig. It uses the same bootstrap process and is fully compatible with existing Factor image files.
Note: Since our aarch64 backend isn’t quite ready to run, testing was done
on an Ubuntu Linux 25.10 on x86_64. We hope for a future release to ship a
native aarch64 backend on macOS.
We are using a recent Zig nightly build:
$ zig version
0.16.0-dev.2915+065c6e794
The Listener – our REPL – works pretty great:
Factor 0.102 x86.64 (2305, heads/master-40edb95d40, Mar 18 2026 17:49:52)
[Zig 0.16.0-dev.2915+065c6e794 ReleaseFast] on linux
IN: scratchpad 1 2 + .
3
IN: scratchpad "hello" length .
5
IN: scratchpad 10 <iota> [ CHAR: a <string> ] map .
{
""
"a"
"aa"
"aaa"
"aaaa"
"aaaaa"
"aaaaaa"
"aaaaaaa"
"aaaaaaaa"
"aaaaaaaaa"
}
IN: scratchpad
At the risk of some distracting language wars, I wanted to particularly highlight some early performance results and other metrics comparing the two implementations: Zig vs C++.
Running the compiler tests – 20% faster:
! Zig VM
IN: scratchpad gc [ "compiler" test ] time
Running time: 9.692492884 seconds
! C++ VM
IN: scratchpad gc [ "compiler" test ] time
Running time: 12.658900299 seconds
Running the core tests – 22% faster:
# Zig VM
$ time ./zig-out/bin/factor -run=tools.test resource:core
real 1m8.776s
user 1m8.519s
sys 0m0.200s
# C++ VM
$ time ./factor -run=tools.test resource:core
real 1m29.935s
user 1m29.429s
sys 0m0.268s
Bootstrapping the Factor environment – 2% faster:
# Zig VM
$ ./zig-out/bin/factor -i=boot.unix-x86.64.image
Core bootstrap completed in 3 minutes and 22 seconds.
# C++ VM
$ ./factor -i=boot.unix-x86.64.image
Core bootstrap completed in 3 minutes and 27 seconds.
Running load-all with the standard library – 8% faster:
! Zig VM
IN: scratchpad gc [ load-all ] time
Running time: 522.531098916 seconds
! C++ VM
IN: scratchpad gc [ load-all ] time
Running time: 569.880413105 seconds
Running the benchmark suite – 13% faster:
! Zig VM
IN: scratchpad gc [ timing-benchmarks ] time
Running time: 438.671019723 seconds
! C++ VM
IN: scratchpad gc [ timing-benchmarks ] time
Running time: 508.235989841 seconds
And you can see that if we sort the benchmarks by percent improvements (worse
to better), some benchmarks that are bignum heavy are much faster, some
are a little faster, and a few have regressed:
Comparing lines of code – 67% more – with fewer files:
# Zig VM
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Language Files Lines Code Comments Blanks
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Zig 51 29034 21032 3761 4241
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total 51 29034 21032 3761 4241
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# C++ VM
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Language Files Lines Code Comments Blanks
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Assembly 1 5 5 0 0
GNU Style Assembly 3 205 150 20 35
C 1 408 323 2 83
C Header 1 270 220 2 48
C++ 58 9911 7597 777 1537
C++ Header 84 5967 4297 606 1064
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total 148 16776 12592 1407 2767
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Comparing binary sizes – 77% larger:
# Zig VM
-rwxrwxr-x 1 user staff 758K Mar 17 11:25 factor
# C++ VM
-rwxrwxr-x 1 user staff 430K Mar 8 13:39 factor
So, where does this go from here?
Well, besides making it also work on Windows, launch graphical programs properly, support compressed images, maybe support 32-bit, generally ensuring that it is a fully bug-free re-implementation of Factor, and investigating why the binaries are larger.
Perhaps it is an opportunity to re-think how the Factor bootstrap process works, to reduce the amount of functions a Factor VM should support, to run Factor in WASM with an optimizing compiler, to implement a simple Factor interpreter, or challenge our assumptions on what it means to be a Factor.
Or, it could just be a fun experiment using Zig. Let’s see!
Standard deviation is “a measure of the amount of variation of the values of a variable about its mean.”. It’s a useful measure from statistics, and std in the math.statistics vocabulary included with Factor.
I bumped into this – um, are they still called tweets? – today:
Word with the lowest standard deviation of letter position in the alphabet, for each length pic.twitter.com/caHYDDpyBx
— Adam Aaronson (@aaaronson) February 4, 2026
Of course, I wondered how that looks for the /usr/share/dict/words on my computer:
IN: scratchpad "/usr/share/dict/words" utf8 file-lines
[ length ] collect-by
[ [ std ] minimum-by ] assoc-map
sort-keys values
[ dup std "%s: %s\n" printf ] each
A: 0.0
aa: 0.0
aba: 0.5773502691896257
baba: 0.5773502691896257
abaca: 0.8944271909999159
bacaba: 0.816496580927726
deedeed: 0.5345224838248488
poroporo: 1.3093073414159542
susurrous: 1.9364916731037085
beefheaded: 1.9578900207451218
cabbagehead: 2.46429041972071
fiddledeedee: 2.424621182533032
promonopolist: 2.911075226502911
monogonoporous: 3.1830595551871363
prophototropism: 3.5023801430836525
philophilosophos: 3.855731664245668
sulphophosphorous: 4.227013964824759
chemicoengineering: 4.556472090169843
plutonometamorphism: 5.220293285659733
encephalomeningocele: 4.871776937249466
philosophicoreligious: 5.014740177478695
philosophicohistorical: 5.485320828241917
philosophicotheological: 5.2090678006509625
scientificophilosophical: 5.538894097749744
antidisestablishmentarianism: 6.7081053397123425
Interesting, both similar and different. Well, it’s pretty close and also pretty obvious we are using slightly different dictionaries. I’m not sure what deedeed or poroporo mean and they aren’t in the SCRABBLE Players Dictionary.
Anyway, fun!
PBRT is an impressive photorealistic rendering system:
From movies to video games, computer-rendered images are pervasive today. Physically Based Rendering introduces the concepts and theory of photorealistic rendering hand in hand with the source code for a sophisticated renderer.
The fourth edition of their book is now available on Amazon as well as freely available online.
I thought it would be fun to explore the PBRT v4 file format using Factor.
Here’s a short example pbrt file from their website:
LookAt 3 4 1.5 # eye
.5 .5 0 # look at point
0 0 1 # up vector
Camera "perspective" "float fov" 45
Sampler "halton" "integer pixelsamples" 128
Integrator "volpath"
Film "rgb" "string filename" "simple.png"
"integer xresolution" [400] "integer yresolution" [400]
WorldBegin
# uniform blue-ish illumination from all directions
LightSource "infinite" "rgb L" [ .4 .45 .5 ]
# approximate the sun
LightSource "distant" "point3 from" [ -30 40 100 ]
"blackbody L" 3000 "float scale" 1.5
AttributeBegin
Material "dielectric"
Shape "sphere" "float radius" 1
AttributeEnd
AttributeBegin
Texture "checks" "spectrum" "checkerboard"
"float uscale" [16] "float vscale" [16]
"rgb tex1" [.1 .1 .1] "rgb tex2" [.8 .8 .8]
Material "diffuse" "texture reflectance" "checks"
Translate 0 0 -1
Shape "bilinearmesh"
"point3 P" [ -20 -20 0 20 -20 0 -20 20 0 20 20 0 ]
"point2 uv" [ 0 0 1 0 1 1 0 1 ]
AttributeEnd
And this is what it might look like:
Using our new pbrt vocabulary, we can convert that text into a set of tuples that we could do computations on, or potentially look into rendering or processing. And, of course, it also supports round-tripping back and forth from text to tuples.
{
T{ pbrt-look-at
{ eye-x 3 }
{ eye-y 4 }
{ eye-z 1.5 }
{ look-x 0.5 }
{ look-y 0.5 }
{ look-z 0 }
{ up-x 0 }
{ up-y 0 }
{ up-z 1 }
}
T{ pbrt-camera
{ type "perspective" }
{ params
{
T{ pbrt-param
{ type "float" }
{ name "fov" }
{ values { 45 } }
}
}
}
}
T{ pbrt-sampler
{ type "halton" }
{ params
{
T{ pbrt-param
{ type "integer" }
{ name "pixelsamples" }
{ values { 128 } }
}
}
}
}
T{ pbrt-integrator { type "volpath" } { params { } } }
T{ pbrt-film
{ type "rgb" }
{ params
{
T{ pbrt-param
{ type "string" }
{ name "filename" }
{ values { "simple.png" } }
}
T{ pbrt-param
{ type "integer" }
{ name "xresolution" }
{ values { 400 } }
}
T{ pbrt-param
{ type "integer" }
{ name "yresolution" }
{ values { 400 } }
}
}
}
}
T{ pbrt-world-begin }
T{ pbrt-light-source
{ type "infinite" }
{ params
{
T{ pbrt-param
{ type "rgb" }
{ name "L" }
{ values { 0.4 0.45 0.5 } }
}
}
}
}
T{ pbrt-light-source
{ type "distant" }
{ params
{
T{ pbrt-param
{ type "point3" }
{ name "from" }
{ values { -30 40 100 } }
}
T{ pbrt-param
{ type "blackbody" }
{ name "L" }
{ values { 3000 } }
}
T{ pbrt-param
{ type "float" }
{ name "scale" }
{ values { 1.5 } }
}
}
}
}
T{ pbrt-attribute-begin }
T{ pbrt-material { type "dielectric" } { params { } } }
T{ pbrt-shape
{ type "sphere" }
{ params
{
T{ pbrt-param
{ type "float" }
{ name "radius" }
{ values { 1 } }
}
}
}
}
T{ pbrt-attribute-end }
T{ pbrt-attribute-begin }
T{ pbrt-texture
{ name "checks" }
{ value-type "spectrum" }
{ class "checkerboard" }
{ params
{
T{ pbrt-param
{ type "float" }
{ name "uscale" }
{ values { 16 } }
}
T{ pbrt-param
{ type "float" }
{ name "vscale" }
{ values { 16 } }
}
T{ pbrt-param
{ type "rgb" }
{ name "tex1" }
{ values { 0.1 0.1 0.1 } }
}
T{ pbrt-param
{ type "rgb" }
{ name "tex2" }
{ values { 0.8 0.8 0.8 } }
}
}
}
}
T{ pbrt-material
{ type "diffuse" }
{ params
{
T{ pbrt-param
{ type "texture" }
{ name "reflectance" }
{ values { "checks" } }
}
}
}
}
T{ pbrt-translate { x 0 } { y 0 } { z -1 } }
T{ pbrt-shape
{ type "bilinearmesh" }
{ params
{
T{ pbrt-param
{ type "point3" }
{ name "P" }
{ values
{ -20 -20 0 20 -20 0 -20 20 0 20 20 0 }
}
}
T{ pbrt-param
{ type "point2" }
{ name "uv" }
{ values { 0 0 1 0 1 1 0 1 } }
}
}
}
}
T{ pbrt-attribute-end }
}
This is available now in the development version of Factor!
Factor has a native ui-backend that allows us to render our UI framework using OpenGL on top of platform-specific APIs for our primary targets of Linux, macOS, and Windows.
On Linux, for a long time that has meant using the
GTK2 library, which has also meant using
X11 and an old library
called libgtkglext which provides a way to use OpenGL within GTK
windows. Well, Linux has moved on and is now pushing
Wayland as the “replacement for the X11
window system protocol and architecture with the aim to be easier to
develop, extend, and maintain”. Most modern Linux distributions have moved
to GTK3 or GTK4 and abstraction libraries like
libepoxy for working with OpenGL and
others for supporting both X11 and Wayland renderers.
I was reminded of this after our recent Factor 0.101 release when someone asked the question:
Does that message mean that Factor still relies on GTK2? IIRC it was EOL:ed around 2020.
Well, this is embarassing – yeah it sure does! Or rather – yes it sure did.
I got motivated to look into what it would take to support GTK3 or GTK4. We had a pull request that was working through adding support for GTK4. After merging that, and modifying it to also provide GTK3 support, I re-discovered that our OpenGL rendering was generally using OpenGL 1.x pipelines and that would not work in a GTK3+ world.
So, after adding OpenGL 3.x support for most of the things our user interface needs, and migrating from GTK 2.x to GTK3, we now have experimental nightly builds using the GTK3 backend:
You can revert to the older GTK2 backend by applying this diff and then performing a fresh bootstrap:
diff --git a/basis/bootstrap/ui/ui.factor b/basis/bootstrap/ui/ui.factor
index 2974e530f9..416704ce29 100644
--- a/basis/bootstrap/ui/ui.factor
+++ b/basis/bootstrap/ui/ui.factor
@@ -12,6 +12,6 @@ IN: bootstrap.ui
{
{ [ os macos? ] [ "ui.backend.cocoa" ] }
{ [ os windows? ] [ "ui.backend.windows" ] }
- { [ os unix? ] [ "ui.backend.gtk3" ] }
+ { [ os unix? ] [ "ui.backend.gtk2" ] }
} cond
] if* require
diff --git a/basis/opengl/gl/extensions/extensions.factor b/basis/opengl/gl/extensions/extensions.factor
index 2d408e93bb..51394eeb4a 100644
--- a/basis/opengl/gl/extensions/extensions.factor
+++ b/basis/opengl/gl/extensions/extensions.factor
@@ -7,7 +7,7 @@ ERROR: unknown-gl-platform ;
<< {
{ [ os windows? ] [ "opengl.gl.windows" ] }
{ [ os macos? ] [ "opengl.gl.macos" ] }
- { [ os unix? ] [ "opengl.gl.gtk3" ] }
+ { [ os unix? ] [ "opengl.gl.gtk2" ] }
[ unknown-gl-platform ]
} cond use-vocab >>
It seems like the newer OpenGL 3.x functions might introduce some lag which is visible when scrolling on some installations, perhaps by not caching certain things that were cached in the OpenGL 1.x code paths. There will need to be some improvements before we are ready to release it, but it is plenty usable as-is.
I also migrated our macOS backend to use the OpenGL 3.x functions as well to allow us to more broadly test and improve these new rendering paths.
This is available in the latest development version.
planet-factor is an Atom/RSS aggregator that collects the contents of Factor-related blogs. It is inspired by Planet Lisp.