Unlike many distributed systems, Chromium is configured in only one place. The configuration mothership is a network service that responds to configuration queries from Chromium nodes, so all configuration for an entire cluster run can be managed in one place. In addition, Chromium configuration is not specified in static files, but rather it is specified programmatically using the Python programming language. If you're not familiar with Python, don't worry, you can almost always create a new configuration script from an existing one without really understanding the semantics of the language. In WireGL, we were always writing homegrown parameterized scripts to generate configuration files; in Chromium, the script is the configuration file.
Let's look at the crdemo.conf script that drove the
Hello, World demo. You should be viewing
this configuration script while reading the description below.
|
1: 2: 3: |
import sys sys.path.append( '../server' ) from mothership import * |
These three lines will appear at the top of every configuration script.
Lines 1 and 2 are Python jargon that allow the interpreter to search another
directory for imported modules. In this case, the module "mothership"
loaded in line 3 is located in the cr/mothership/server directory.
Line 3 imports all of the symbols from the "mothership"
Python module. We say "from mothership import *"
instead of "import mothership" so that we can refer to
elements of the mothership module without explicitly qualifying their
names. For example, later we will see references to "SPU"
objects, which would be "mothership.SPU" objects had we
not done our import this way.
|
5: 6: 7: |
if len(sys.argv) > 3 or len(sys.argv) < 2: print 'Usage: %s sys.exit(-1) |
Lines 5-7 make sure that the arguments to the script are correct.
Recall that this script determines which program to run by its first argument, so
"sys.argv" (analogous to the "argv"
parameter to "main" in C/C++) must have at least two
elements (including the name of the script). This script can take an
optional third argument specifying the SPU to be loaded by the client
node. Notice, by the way, that Python delimits blocks by indentation only;
there are no curly braces or "endif"s to mark the end of
things. Be extremely careful about mixing tabs and spaces (i.e., don't do
it).
|
9: 10: |
CR_BIN_DIR = r'c:\work\cr\bin\WIN_NT' demo = sys.argv[1] |
Lines 9-10 set up two important global variables. "CR_BIN_DIR"
is the directory containing the demo to be run. "demo"
is the first script argument, or the name of the program to be run. This,
for example, would hold the value "fonttest" in the Hello,
World example.
|
12: 13: 14: 15: |
if len(sys.argv) == 3: clientspuname = sys.argv[2] else: clientspuname = 'pack' |
Lines 12-15 figure out which SPU will be loaded by the application. If
the user has specified three arguments to the script, then the third one will be
the name of the spu. Otherwise, it defaults to "pack".
|
17: 18: |
server_spu = SPU( 'render' ) client_spu = SPU( clientspuname ) |
Lines 17-18 create "SPU" objects. These are the
plug-in modules that implement the OpenGL API. In the Hello,
World example, we created two SPU's: a "render" spu that
dispatches OpenGL calls to the system's implementation, and a "pack"
SPU that encodes its commands and sends them over the network verbatim (i.e.,
with [almost] no analysis). Each SPU in a system needs to have a separate
"SPU" object created for it in the configuration
script. Notice that we are using the client SPU name that was computed in
lines 12-15.
| 20: | server_spu.Conf( 'window_geometry', 100, 100, 500, 500 ) |
Once the SPU's are created, they can be configured. In this case, the "render" SPU needs to know what kind of window to create. This directive tells the "render" SPU to create a window that is 500 pixels wide and 500 pixels high, and is shown at an offset of (100,100) from the upper-left corner of the screen. This is all the SPU configuration that happens in this script, because the client SPU needs no configuration. A complete list of all available configuration parameters for all provided SPU's is given in the "Configuration options for Provided SPU's" section.
|
22: 23: |
server_node = CRNetworkNode( ) server_node.AddSPU( server_spu ) |
Once all the SPU's have been defined and configured, it's now time to
describe the graph of nodes itself. The server node is created as an
instance of a "CRNetworkNode" object. Notice that
no parameters are given to the constructor for this object. There are
two optional parameters that can be passed to the constructor: a
"hostname" parameter indicating the name of the computer on which this
node will be running, and a "port" parameter indicating what port it
should listen on for clients. If no hostname is provided, the default is 'localhost'.
The default port is 7000.
Each node can have a chain of SPU's attached to it. The order of
the chain is determined by the order of calls to the "AddSPU"
method. Here, the server node only has one SPU, so order is irrelevant.
|
25: 26: |
if (clientspuname == 'tilesort' ): server_node.AddTile( 0, 0, 500, 500 ) |
If the client SPU is the "tilesort" SPU (used for rendering to
tiled displays), the tiling of the logical output space must be provided.
This information is mandatory even if there is only one tile, since the absence
of such a tile will make the server behave in slightly different ways (in
particular, with respect to the glViewport and glScissor
calls). Notice that the tiling information is associated with the server
node, not the "render" SPU.
|
28: 29: 30: |
client_node = CRApplicationNode( ) client_node.AddSPU( client_spu ) client_spu.AddServer( server_node, 'tcpip' ) |
Now that the server node has been completely defined, we define the client
node. Notice that lines 28 and 29 are almost identical to lines 22 and 23,
except the client node is defined as an instance of a "CRApplicationNode"
object. Line 30 adds the server node (defined on line 22) to the client
SPU. Recall that the client SPU defaults to the "pack"
SPU, or the "tilesort" SPU could be used instead. In fact, it is possible to make the client SPU the "render"
SPU, in which case the server is not needed. In such a case, calling
"AddServer" won't hurt anything, so we always create a
client-server relationship, regardless of the client SPU being used. This is in contrast to lines 25 and 26, where
we don't want to define a tiling if we're not using a tiling-aware client SPU.
|
32: 33: |
client_node.SetApplication( '%s\%s' % (CR_BIN_DIR, demo) ) client_node.StartDir( CR_BIN_DIR ) |
So far, the node graph and SPU collection have been completely generic.
Lines 32 and 33 bind a specific application to the client node. Line 32
tells the application faker running on the client node which application to
run. Line 33 tells the application faker to change directories to "CR_BIN_DIR"
before launching the application. This is an optional step, but it is
useful for applications that need to find certain input files, such as textures
or application configuration information. If this line is omitted, the
specified application will run in whatever directory "crappfaker"
was run from.
|
35: 36: 37: 38: 39: |
cr = CR() cr.MTU( 1024*1024 ); cr.AddNode( client_node ) cr.AddNode( server_node ) cr.Go() |
The final 5 lines of the configuration script set everything in motion.
Line 35 creates a "CR" object, which is the network-aware
mothership that will respond to queries about the nodes it manages. The
mothership has a single global parameter, the "MTU", set
on line 36. This is the largest buffer size that is allowed to pass
between a client and a server (in future versions of Chromium, this
parameter may be specified in a different way). Lines 37 and 38 add the two
nodes we have created to the mothership. Finally, the "Go"
method is called, which will loop forever, answering configuration queries over
the network.
Although this configuration script is quite simple, it exercises all of the
features of the mothership scripting environment. An augmented version of
this configuration file is available as crdemo_full.conf.
This version adds two more SPU's, one at each node. These SPU's, called
"print" SPU's, generate human-readable dumps of the OpenGL stream to
log files for debugging or analysis. The reader should look at
both crdemo_full.conf and crdemo.conf
and make sure that the differences are clear.