From 32ef5db4c6df283c6f02d30429a3b4195cc13c9c Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 23 Sep 2021 19:12:03 -0400 Subject: doc: Reorganize and expand manual. --- doc/api.texi | 1746 ++++++++++++++++++++++++++++++---------------------- doc/chickadee.texi | 55 +- 2 files changed, 1072 insertions(+), 729 deletions(-) diff --git a/doc/api.texi b/doc/api.texi index d435a3b..6a1361d 100644 --- a/doc/api.texi +++ b/doc/api.texi @@ -1,7 +1,7 @@ @menu * Kernel:: The fundamental components. * Math:: Linear algebra, spatial partitioning, and more. -* Graphics:: Eye candy. +* Graphics:: 2D and 3D rendering. * Audio:: Make some noise. * Scripting:: Bringing the game world to life. @end menu @@ -16,7 +16,6 @@ game loop and desktop environment interaction. * The Game Loop:: The core event loop. * Input Devices:: Mouse, keyboard, controller input. * Window Manipulation:: Inspect and modify the graphical window. -* Live Coding:: Tips for building games from the REPL. @end menu @node The Game Loop @@ -516,43 +515,6 @@ Enable or disable fullscreen mode for @var{window}. If it is enabled. @end deffn - -@node Live Coding -@subsection Live Coding - -One of the biggest appeals of any Lisp dialect is the ability to use -the ``read-eval-print loop'' (REPL for short) to build programs -iteratively and interactively while the program is running. However, -programs that run in an event loop and respond to user input (such as -a video game) require special care for this workflow to be pleasant. -Chickadee provides no built-in support for live coding, but it's -fairly easy to hook up a special kind of REPL yourself. - -First, create a cooperative REPL server (It's important to use Guile's -cooperative REPL server instead of the standard REPL server in -@code{(system repl server)} to avoid thread synchronization issues). -Then, in the game loop's update procedure, call -@code{poll-coop-repl-server} and pass the REPL object. Here is a -template to follow: - -@example -(use-modules (chickadee) - (system repl coop-server)) - -(define repl (spawn-coop-repl-server)) - -(define (update dt) - (poll-coop-repl-server repl) - ...) - -(run-game #:update update ...) -@end example - -To use the REPL, connect to it via port 37146. Telnet will do the -trick, but using the @uref{https://www.nongnu.org/geiser/, Geiser} -extension for Emacs is by far the best way to develop at the REPL with -Guile. Use @code{M-x connect-to-guile} to connect to the REPL server. - @node Math @section Math @@ -1608,7 +1570,6 @@ blocks to implement additional rendering techniques. * Colors:: Such pretty colors... * Textures:: 2D images, tile atlases, cube maps, etc. * Sprites:: Draw 2D images. -* 9-Patches:: Scalable bitmap boxes. * Fonts:: Drawing text. * Vector Paths:: Draw filled and stroked paths. * Particles:: Pretty little flying pieces! @@ -1617,12 +1578,12 @@ blocks to implement additional rendering techniques. * Lights:: Dynamic 3D lighting. * Skyboxes:: Backgrounds for 3D environments. * Meshes:: 3D meshes and materials. -* Blending:: Control how pixels are combined. +* Buffers:: Send data to the GPU. +* Shaders:: Create custom GPU programs. * Framebuffers:: Render to texture. * Viewports:: Restrict rendering to a particular area. +* Render Settings:: Depth tests, stencil tests, polygon modes, etc. * Rendering Engine:: Rendering state management. -* Buffers:: Send data to the GPU. -* Shaders:: Create custom GPU programs. @end menu @node Colors @@ -1936,6 +1897,46 @@ Return the width of @var{texture} in pixels. Return the height of @var{texture} in pixels. @end deffn +@deffn {Procedure} current-texture-0 +Return the current texture associated with texture unit 0 on the GPU. +@end deffn + +@deffn {Procedure} current-texture-1 +Return the current texture associated with texture unit 1 on the GPU. +@end deffn + +@deffn {Procedure} current-texture-2 +Return the current texture associated with texture unit 2 on the GPU. +@end deffn + +@deffn {Procedure} current-texture-3 +Return the current texture associated with texture unit 3 on the GPU. +@end deffn + +@deffn {Procedure} current-texture-4 +Return the current texture associated with texture unit 4 on the GPU. +@end deffn + +@defvar g:texture-0 +Render state for texture unit 0 (@pxref{Rendering Engine}.) +@end defvar + +@defvar g:texture-1 +Render state for texture unit 1 (@pxref{Rendering Engine}.) +@end defvar + +@defvar g:texture-2 +Render state for texture unit 2 (@pxref{Rendering Engine}.) +@end defvar + +@defvar g:texture-3 +Render state for texture unit 3 (@pxref{Rendering Engine}.) +@end defvar + +@defvar g:texture-4 +Render state for texture unit 4 (@pxref{Rendering Engine}.) +@end defvar + @subsubsection Tile Atlases It is common practice to combine multiple bitmap images into a single @@ -2034,6 +2035,8 @@ The area drawn to is as big as the texture, by default. To draw to an arbitrary section of the screen, specify @var{rect}. @end deffn +@subsubsection Sprite Batches + It's not uncommon to need to draw hundreds or thousands of sprites each frame. However, GPUs (graphics processing units) are tricky beasts that prefer to be sent few, large chunks of data to render @@ -2089,8 +2092,7 @@ Render @var{batch} using @var{blend-mode}. Alpha blending is used by default. @end deffn -@node 9-Patches -@subsection 9-Patches +@subsubsection 9-Patches A 9-patch is a method of rendering a texture so that it can be stretched to cover an area of any size without becoming distorted. @@ -2110,7 +2112,8 @@ the top and bottom sides, which can be stretched or tiled horizontally The most common application of this technique is for graphical user interface widgets like buttons and dialog boxes which are often dynamically resizable. By using a 9-patch, they can be rendered at -any size without scaling artifacts. +any size without scaling artifacts. The @code{(chickadee graphics +9-patch)} module provides this functionality. @deffn {Procedure} draw-9-patch texture rect @ [#:margin 0] [#:top-margin margin] [#:bottom-margin margin] @ @@ -2132,8 +2135,8 @@ To make margins of differing sizes, the @var{top-margin}, @var{bottom-margin}, @var{left-margin}, and @var{right-margin} arguments may be used. -Refer to @code{draw-sprite} (@pxref{Sprites}) for information about -the other arguments as they are the same. +Refer to @code{draw-sprite} for information about the other arguments +as they are the same. @end deffn @node Fonts @@ -2344,12 +2347,12 @@ Apply all the given style settings to @var{painter}. Possible style attributes are: @itemize -@item blend-mode -@item fill-color -@item stroke-color -@item stroke-width -@item stroke-feather -@item stroke-cap +@item @code{blend-mode} +@item @code{fill-color} +@item @code{stroke-color} +@item @code{stroke-width} +@item @code{stroke-feather} +@item @code{stroke-cap} @end itemize @example @@ -2579,8 +2582,8 @@ Return a new particle system that may contain up to @var{capacity} particles. Achieving the desired particle effect involves tweaking the following keyword arguments as needed: -- @var{blend-mode}: Pixel blending mode. Alpha blending is used by default. -(@pxref{Blending} for more about blend modes). +- @var{blend-mode}: Pixel blending mode. Alpha blending is used by +default. (@pxref{Render Settings} for more about blend modes). - @var{start-color}: The tint color of the particle at the beginning of its life. White by default. @@ -2988,20 +2991,23 @@ Emits light of a constant intensity in a direction, like sunlight. Emits a cone of light in a direction, like a flashlight. @end itemize -@deffn {Procedure} make-point-light [#:position (vec3 0 0 0)] [#:color black] +@deffn {Procedure} make-point-light [#:position (vec3 0 0 0)] [#:color black] @ + [#:intensity 1] + Return a new point light located at @var{position} that emits @var{color} light. @end deffn @deffn {Procedure} make-directional-light [#:direction (vec3 0 -1 0)] @ - [#:color black] + [#:color black] [#:intensity 1] Return a new directional light that emits @var{color} light in @var{direction}. @end deffn @deffn {Procedure} make-spot-light [#:position (vec3 0 0 0)] @ - [#:direction (vec3 0 -1 0)] [#:color black] [#:cut-off @math{π/4}] + [#:direction (vec3 0 -1 0)] [#:color black] [#:cut-off @math{π/4}] @ + [#:intensity 1] Return a new spot light located at @var{position} that emits @var{color} light in @var{direction} in a cone that cuts off at @@ -3035,6 +3041,10 @@ Return the type of @var{light}, one of: Return the color of @var{light}. @end deffn +@deffn {Procedure} light-intensity light +Return the intensity of @var{light}. +@end deffn + @deffn {Procedure} light-position light Return the position of @var{light}. The value is irrelevant for directional lights. @@ -3046,8 +3056,8 @@ point lights. @end deffn @deffn {Procedure} light-cut-off light -Return the cut off angle of @var{light}. The value is only relevant -for spot lights. +Return the cosine of the cut off angle of @var{light}. The value is +only relevant for spot lights. @end deffn @deffn {Procedure} set-light-color! light color @@ -3377,924 +3387,1218 @@ triangles in the resulting sphere increases exponentially with each increment to @var{quality}. @end deffn -@node Blending -@subsection Blending +@node Buffers +@subsection Buffers -Rendering a scene often involves drawing layers of objects that -overlap each other. Blending determines how two overlapping pixels -are combined in the final image that is rendered to the screen. +Alright, let's brush aside all of those pretty high level abstractions +and discuss what is going on under the hood. The GPU exists as a +discrete piece of hardware separate from the CPU. In order to make it +draw things, we must ship lots of data out of our memory space and +into the GPU. The @code{(chickadee graphics buffer}) module provides an +API for manipulating GPU buffers. -Chickadee provides the following blend modes: +In OpenGL terminology, a chunk of data allocated on the GPU is a +``vertex buffer object'' or VBO. For example, here is a bytevector +that could be transformed into a GPU buffer that packs together vertex +position and texture coordinates: -@defvar blend:alpha -Blend pixels according to the values of their alpha channels. This is -the most commonly used blend mode. -@end defvar +@example +(use-modules (chickadee graphics buffer) (srfi srfi-4)) +(define data + (f32vector -8.0 -8.0 ; 2D vertex + 0.0 0.0 ; 2D texture coordinate + 8.0 -8.0 ; 2D vertex + 1.0 0.0 ; 2D texture coordinate + 8.0 8.0 ; 2D vertex + 1.0 1.0 ; 2D texture coordinate + -8.0 8.0 ; 2D vertex + 0.0 1.0)) ; 2D texture coordinate +@end example -@defvar blend:replace -Overwrite the output pixel color with the color being drawn. -@end defvar +This data represents a textured 16x16 square centered on the +origin. To send this data to the GPU, the @code{make-buffer} procedure +is needed: -@defvar blend:add -Add all pixel color values together. The more colors blended -together, the more white the final color becomes. -@end defvar +@example +(define buffer (make-buffer data #:stride 16)) +@end example -@defvar blend:subtract -Subtract all pixel color values. The more colors blended together, -the more black the final color becomes. -@end defvar +The @code{#:stride} keyword argument indicates how many bytes make up +each element of the buffer. In this case, there are 4 floats per +element: 2 for the vertex, and 2 for the texture coordinate. A 32-bit +float is 4 bytes in length, so the buffer's stride is 16. -@defvar blend:multiply -@end defvar +Within a VBO, one or more ``attributes'', as OpenGL calls them, may be +present. Attributes are subregions within the buffer that have a +particular data type. In this case, there are two attributes packed +into the buffer. To define vertex attributes, the +@code{make-vertex-attribute} procedure is needed: -@defvar blend:darken -@end defvar +@example +(define vertices + (make-vertex-attribute #:buffer buffer + #:type 'vec2 + #:component-type 'float + #:length 4)) +(define texcoords + (make-vertex-attribute #:buffer buffer + #:type 'vec2 + #:component-type 'float + #:length 4 + #:offset 8)) +@end example -@defvar blend:lighten -@end defvar +To render a square, the GPU needs to draw two triangles, which means +we need 6 vertices in total. However, the above buffer only contains +data for 4 vertices. This is because there are only 4 unique vertices +for a square, but 2 of them must be repeated for each triangle. To +work with deduplicated vertex data, an ``index buffer'' must be +created. -@defvar blend:screen -@end defvar +@example +(define index-buffer + (make-buffer (u32vector 0 3 2 0 2 1) + #:target 'index) +(define indices + (make-vertex-attribute #:type 'scalar + #:component-type 'unsigned-int + #:buffer index-buffer)) +@end example -Custom blend modes can be created using the @code{make-blend-mode} procedure: +Note the use of the @code{#:target} keyword argument. It is required +because the GPU treats index data in a special way and must be told +which data is index data. -@deffn {Procedure} make-blend-mode equation source-function destination-function -Return a new custom blend mode that applies @var{source-function} to -the source color, @var{destination-function} to the destination color, -and finally applies @var{equation} to the transformed -source/destination color values. These arguments are @emph{not} -procedures, but symbolic representations of the functions that OpenGL -supports. +Now that the vertex attributes have been created, all that's left is +to bind them all together in a vertex array. Vertex arrays associate +each vertex attribute with an attribute index on the GPU. The indices +that are chosen must correspond with the indices that the shader +(@pxref{Shaders}) expects for each attribute. -Valid values for @var{equation} are: +@example +(define vertex-array + (make-vertex-array #:indices indices + #:attributes `((0 . ,vertices) + (1 . ,texcoords)))) +@end example -@itemize -@item @code{add} -@item @code{subtract} -@item @code{reverse-subtract} -@item @code{min} -@item @code{max} -@item @code{alpha-min} -@item @code{alpha-max} -@end itemize +With the vertex array created, the GPU is now fully aware of how to +interpret the data that it has been given in the original buffer. +Actually rendering this square is left as an exercise to the reader. +See the @ref{Shaders} section and the @code{shader-apply} procedure in +@ref{Rendering Engine} for the remaining pieces of a successful draw +call. Additionally, consider reading the source code for sprites, +shapes, or particles to see GPU buffers in action. -Valid values for @var{source-function} are: +Without further ado, the API reference: + +@deffn {Procedure} make-buffer data [#:name "anonymous"] @ + [#:length] [#:offset 0] [#:stride 0] [#:target @code{vertex}] @ + [#:usage @code{static}] + +Upload @var{data}, a bytevector, to the GPU. By default, the entire +bytevector is uploaded. A subset of the data may be uploaded by +specifying the @var{offset}, the index of the first byte to be +uploaded, and @var{length}, the number of bytes to upload. + +If @var{data} is @code{#f}, allocate @var{length} bytes of fresh GPU +memory instead. + +@var{target} and @var{usage} are hints that tell the GPU how the +buffer is intended to be used. + +@var{target} may be: @itemize -@item @code{zero} -@item @code{one} -@item @code{destination-color} -@item @code{one-minus-destination-color} -@item @code{source-alpha-saturate} -@item @code{source-alpha} -@item @code{one-minus-source-alpha} -@item @code{destination-alpha} -@item @code{one-minus-destination-alpha} -@item @code{constant-color} -@item @code{one-minus-constant-color} -@item @code{constant-alpha} -@item @code{one-minus-constant-alpha} +@item @code{vertex} +Vertex attribute data. + +@item @code{index} +Index buffer data. + @end itemize -Valid values for @var{destination-function} are: +@var{usage} may be: @itemize -@item @code{zero} -@item @code{one} -@item @code{source-color} -@item @code{one-minus-source-color} -@item @code{source-alpha} -@item @code{one-minus-source-alpha} -@item @code{destination-alpha} -@item @code{one-minus-destination-alpha} -@item @code{constant-color} -@item @code{one-minus-constant-color} -@item @code{constant-alpha} -@item @code{one-minus-constant-alpha} +@item @code{static} +The buffer data will not be modified after creation. + +@item @code{stream} +The buffer data will be modified frequently. + @end itemize +@var{name} is simply an arbitrary string for debugging purposes that +is never sent to the GPU. @end deffn -@node Framebuffers -@subsection Framebuffers - -A framebuffer is a chunk of memory that the GPU can render things -onto. By default, the framebuffer that is used for rendering is the -one belonging to the game window, but custom framebuffers can be used -as well. A common use-case for custom framebuffers is applying -post-processing effects: The entire scene is rendered to a -framebuffer, and then the contents of that framebuffer are applied to -a post-processing shader and rendered to the game window. The -post-processing shader could do any number of things: scaling, -antialiasing, motion blur, etc. +@deffn {Procedure} buffer? obj +Return @code{#t} if @var{obj} is a GPU buffer. +@end deffn -@deffn {Procedure} make-framebuffer width height @ - [#:min-filter @code{linear}] @ - [#:mag-filter @code{linear}] @ - [#:wrap-s @code{repeat}] @ - [#:wrap-t @code{repeat}] +@deffn {Procedure} index-buffer? buffer +Return @code{#t} if @var{buffer} is an index buffer. +@end deffn -Create a new framebuffer that is @var{width} pixels wide and @var{height} pixels high. - -@var{min-filter} and @var{mag-filter} determine the scaling algorithm -applied to the framebuffer when rendering. By default, linear scaling -is used in both cases. To perform no smoothing at all, use -@code{nearest} for simple nearest neighbor scaling. This is typically -the best choice for pixel art games. -@end deffn - -@deffn {Procedure} framebuffer? obj -Return @code{#t} if @var{obj} is a framebuffer. -@end deffn +@defvar null-buffer +Represents the absence of a buffer. +@end defvar -@deffn {Procedure} framebuffer-texture fb -Return the texture backing the framebuffer @var{fb}. +@deffn {Procedure} buffer-name buffer +Return the name of @var{buffer}. @end deffn -@deffn {Procedure} framebuffer-viewport fb -Return the default viewport (@pxref{Viewports}) used by the -framebuffer @var{fb}. +@deffn {Procedure} buffer-length buffer +Return the length of @var{buffer}. @end deffn -@deffn {Procedure} null-framebuffer -The default framebuffer. +@deffn {Procedure} buffer-stride buffer +Return the amount of space, in bytes, between each element in +@var{buffer}. @end deffn -@node Viewports -@subsection Viewports - -A viewport represents a subset of the screen (or framebuffer). When -rendering a frame, the resulting image will only appear within that -viewport. These aren't often needed, and Chickadee's default viewport -occupies the entire screen, but there are certain situations where -they are useful. For example, a split-screen multiplayer game may -render to two different viewports, each occupying a different half of -the screen. For information about how to set the current viewport, -see @code{with-viewport} in @ref{Rendering Engine}. - -The @code{(chickadee graphics viewport)} module provides the following -API: - -@deffn {Procedure} make-viewport x y width height @ - [#:clear-color] [#:clear-flags] - -Create a viewport that covers an area of the window starting from -coordinates (@var{x}, @var{y}) and spanning @var{width} @code{x} -@var{height} pixels. Fill the viewport with @var{clear-color} when -clearing the screen. Clear the buffers denoted by the list of symbols -in @var{clear-flags}. - -Possible values for @var{clear-flags} are @var{color-buffer}, -@var{depth-buffer}, @var{accum-buffer}, and @var{stencil-buffer}. +@deffn {Procedure} buffer-target buffer +Return the the intended usage of @var{buffer}, either @code{vertex} or +@code{index}. @end deffn -@deffn {Procedure} viewport? obj -Return @code{#t} if @var{obj} is a viewport. +@deffn {Procedure} buffer-usage buffer +Return the intended usage of @var{buffer}, either @code{static} for +buffer data that will not change once sent to the GPU, or +@code{stream} for buffer data that will be frequently updated from the +client-side. @end deffn -@deffn {Procedure} viewport-x viewport -Return the left edge of @var{viewport}. +@deffn {Procedure} buffer-data buffer +Return a bytevector containing all the data within @var{buffer}. If +@var{buffer} has not been mapped (see @code{with-mapped-buffer}) then +this procedure will return @code{#f}. @end deffn -@deffn {Procedure} viewport-y viewport -Return the bottom edge of @var{viewport}. -@end deffn +@deffn {Syntax} with-mapped-buffer buffer body @dots{} +Evaluate @var{body} in the context of @var{buffer} having its data +synced from GPU memory to RAM. In this context, @code{buffer-data} +will return a bytevector of all the data stored in @var{buffer}. When +program execution exits this form, the data (including any +modifications) is synced back to the GPU. -@deffn {Procedure} viewport-width viewport -Return the width of @var{viewport}. +@deffn {Procedure} current-buffer +Return the current buffer. @end deffn -@deffn {Procedure} viewport-height viewport -Return the height of @var{viewport}. -@end deffn +@defvar g:buffer +Render state for buffers (@pxref{Rendering Engine}.) +@end defvar -@deffn {Procedure} viewport-clear-color viewport -Return the clear color for @var{viewport}. +This form is useful for streaming buffers that need to update their +contents dynamically, such as a sprite batch. @end deffn -@deffn {Procedure} viewport-clear-flags viewport -Return the list of clear flags for @var{viewport}. -@end deffn +@deffn {Procedure} make-vertex-attribute #:buffer #:type @ + #:component-type #:length [#:offset @code{0}] [#:divisor @code{1}] @ + [#:name @code{"anonymous"}] -@node Rendering Engine -@subsection Rendering Engine +Return a new vertex attribute for @var{buffer} starting at byte index +@var{offset} of @var{length} elements, where each element is of +@var{type} and composed of @var{component-type} values. -Chickadee defines rendering using a metaphor familiar to Scheme -programmers: procedure application. A shader (@pxref{Shaders}) is -like a procedure for the GPU to apply. Shaders are passed arguments: -A vertex array containing the geometry to render (@pxref{Buffers}) and -zero or more keyword arguments that the shader understands. Similar -to how Scheme has @code{apply} for calling procedures, Chickadee -provides @code{shader-apply} for calling shaders. +Valid values for @var{type} are: -Additionally, there is some dynamic state that effects how -@code{shader-apply} will behave. Things like the current viewport, -framebuffer, and blend mode are stored as dynamic state because it -would be tedious to have to have to specify them each time -@code{shader-apply} is called. +@itemize +@item @code{scalar} +single number -The following procedures and syntax can be found in the -@code{(chickadee graphics)} module. +@item @code{vec2} +2D vector -@deffn {Syntax} shader-apply shader vertex-array @ - [#:uniform-key uniform-value @dots{}] -@deffnx {Syntax} shader-apply* shader vertex-array count @ - [#:uniform-key uniform-value @dots{}] +@item @code{vec3} +3D vector -Render @var{vertex-array} using @var{shader} with the uniform values -specified in the following keyword arguments. +@item @code{vec4} +4D vector -While @code{shader-apply} will draw every vertex in @var{vertex-array}, -@code{shader-apply*} will only draw @var{count} vertices. -@end deffn +@item @code{mat2} +2x2 matrix -@deffn {Syntax} shader-apply/instanced shader vertex-array @ - n [#:uniform-key uniform-value @dots{}] -@deffnx {Syntax} shader-apply/instanced shader vertex-array @ - count n [#:uniform-key uniform-value @dots{}] +@item @code{mat3} +3x3 matrix -Render @var{vertex-array} @var{n} times using @var{shader} with the -uniform values specified in the following keyword arguments. +@item @code{mat4} +4x4 matrix +@end itemize -Instanced rendering is very beneficial for rendering the same object -many times with only small differences for each one. For example, the -particle effects described in @ref{Particles} use instanced rendering. +Valid values for @var{component-type} are: -While @code{shader-apply/instanced} will draw every vertex in -@var{vertex-array}, @code{shader-apply*} will only draw @var{count} -vertices. -@end deffn +@itemize -@deffn {Procedure} current-viewport -Return the currently bound viewport (@pxref{Viewports}). -@end deffn +@item @code{byte} +@item @code{unsigned-byte} +@item @code{short} +@item @code{unsigned-short} +@item @code{int} +@item @code{unsigned-int} +@item @code{float} +@item @code{double} -@deffn {Procedure} current-framebuffer -Return the currently bound framebuffer (@pxref{Framebuffers}). -@end deffn +@end itemize -@deffn {Procedure} current-blend-mode -Return the currently bound blend mode (@pxref{Blending}). +@var{divisor} is only needed for instanced rendering applications (see +@code{shader-apply/instanced} in @ref{Rendering Engine}) and represents +how many instances each vertex element applies to. A divisor of 0 +means that a single element is used for every instance and is used for +the data being instanced. A divisor of 1 means that each element is +used for 1 instance. A divisor of 2 means that each element is used +for 2 instances, and so on. @end deffn -@deffn {Procedure} current-depth-test -Return @code{#t} if depth testing is currently enabled (@pxref{Blending}). +@deffn {Procedure} vertex-attribute? obj +Return @code{#t} if @var{obj} is a vertex attribute. @end deffn -@deffn {Procedure} current-texture -Return the currently bound texture (@pxref{Textures}). +@deffn {Procedure} vertex-attribute->buffer vertex-attribute +Return the buffer that @var{vertex-attribute} is using. @end deffn -@deffn {Procedure} current-projection -Return the currently bound projection matrix (@pxref{Matrices}). +@deffn {Procedure} vertex-attribute-name vertex-attribute +Return the name of @var{vertex-attribute}. @end deffn -@deffn {Syntax} with-viewport viewport body @dots{} -Evaluate @var{body} with the current viewport bound to @var{viewport} (@pxref{Viewports}). +@deffn {Procedure} vertex-attribute-offset vertex-attribute +Return the byte offset of @var{vertex-attribute}. @end deffn -@deffn {Syntax} with-framebuffer framebuffer body @dots{} -Evaluate @var{body} with the current framebuffer bound to -@var{framebuffer} (@pxref{Framebuffers}). +@deffn {Procedure} vertex-attribute-type vertex-attribute +Return the data type of @var{vertex-attribute}. @end deffn -@deffn {Syntax} with-blend-mode blend-mode body @dots{} -Evaluate @var{body} with the current blend mode bound to -@var{blend-mode} (@pxref{Blending}). +@deffn {Procedure} vertex-attribute-component-type vertex-attribute +Return the component data type of @var{vertex-attribute} @end deffn -@deffn {Syntax} with-depth-test depth-test? body @dots{} -Evaluate @var{body} with the depth-test disabled if @var{depth-test?} -is @code{#f}, or enabled otherwise (@pxref{Blending}). +@deffn {Procedure} vertex-attribute-divisor vertex-attribute +Return the instance divisor for @var{vertex-attribute}. @end deffn -@deffn {Syntax} with-texture texture body @dots{} -Evaluate @var{body} with the current texture bound to @var{texture} -(@pxref{Textures}). -@end deffn +@deffn {Syntax} with-mapped-vertex-attribute vertex-attribute body @dots{} -@deffn {Syntax} with-projection projection body @dots{} -Evaluate @var{body} with the current projection matrix bound to -@var{projection} (@pxref{Matrices}). +Evaluate @var{body} in the context of @var{vertex-attribute} having +its data synced from GPU memory to RAM. See @code{with-mapped-buffer} +for more information. @end deffn -@node Buffers -@subsection Buffers +@deffn {Procedure} make-vertex-array #:indices #:attributes @ + [#:mode @code{triangles}] -Alright, let's brush aside all of those pretty high level abstractions -and discuss what is going on under the hood. The GPU exists as a -discrete piece of hardware separate from the CPU. In order to make it -draw things, we must ship lots of data out of our memory space and -into the GPU. The @code{(chickadee graphics buffer}) module provides an -API for manipulating GPU buffers. +Return a new vertex array using the index data within the vertex +attributes @var{indices} and the vertex attribute data within +@var{attributes}. -In OpenGL terminology, a chunk of data allocated on the GPU is a -``vertex buffer object'' or VBO. For example, here is a bytevector -that could be transformed into a GPU buffer that packs together vertex -position and texture coordinates: +@var{attributes} is an alist mapping shader attribute indices to +vertex attributes: @example -(use-modules (chickadee graphics buffer) (srfi srfi-4)) -(define data - (f32vector -8.0 -8.0 ; 2D vertex - 0.0 0.0 ; 2D texture coordinate - 8.0 -8.0 ; 2D vertex - 1.0 0.0 ; 2D texture coordinate - 8.0 8.0 ; 2D vertex - 1.0 1.0 ; 2D texture coordinate - -8.0 8.0 ; 2D vertex - 0.0 1.0)) ; 2D texture coordinate +`((1 . ,vertex-attribute-a) + (2 . ,vertex-attribute-b) + @dots{}) @end example -This data represents a textured 16x16 square centered on the -origin. To send this data to the GPU, the @code{make-buffer} procedure -is needed: +By default, the vertex array is interpreted as containing a series of +triangles. If another primtive type is desired, the @var{mode} +keyword argument may be overridden. The following values are +supported: -@example -(define buffer (make-buffer data #:stride 16)) -@end example +@itemize +@item @code{points} +@item @code{lines} +@item @code{line-loop} +@item @code{line-strip} +@item @code{triangles} +@item @code{triangle-strip} +@item @code{triangle-fan} +@end itemize -The @code{#:stride} keyword argument indicates how many bytes make up -each element of the buffer. In this case, there are 4 floats per -element: 2 for the vertex, and 2 for the texture coordinate. A 32-bit -float is 4 bytes in length, so the buffer's stride is 16. +@end deffn -Within a VBO, one or more ``attributes'', as OpenGL calls them, may be -present. Attributes are subregions within the buffer that have a -particular data type. In this case, there are two attributes packed -into the buffer. To define vertex attributes, the -@code{make-vertex-attribute} procedure is needed: +@defvar null-vertex-array +Represents the absence of a vertex array. +@end defvar -@example -(define vertices - (make-vertex-attribute #:buffer buffer - #:type 'vec2 - #:component-type 'float - #:length 4)) -(define texcoords - (make-vertex-attribute #:buffer buffer - #:type 'vec2 - #:component-type 'float - #:length 4 - #:offset 8)) -@end example +@deffn {Procedure} vertex-array? obj +Return @code{#t} if @var{obj} is a vertex array. +@end deffn -To render a square, the GPU needs to draw two triangles, which means -we need 6 vertices in total. However, the above buffer only contains -data for 4 vertices. This is because there are only 4 unique vertices -for a square, but 2 of them must be repeated for each triangle. To -work with deduplicated vertex data, an ``index buffer'' must be -created. +@deffn {Procedure} vertex-array-indices vertex-array +Return the buffer view containing index data for @var{vertex-array}. +@end deffn -@example -(define index-buffer - (make-buffer (u32vector 0 3 2 0 2 1) - #:target 'index) -(define indices - (make-vertex-attribute #:type 'scalar - #:component-type 'unsigned-int - #:buffer index-buffer)) -@end example +@deffn {Procedure} vertex-array-attributes vertex-array +Return the attribute index -> buffer view mapping of vertex attribute +data for @var{vertex-array}. +@end deffn -Note the use of the @code{#:target} keyword argument. It is required -because the GPU treats index data in a special way and must be told -which data is index data. +@deffn {Procedure} vertex-array-mode vertex-array +Return the primitive rendering mode for @var{vertex-array}. +@end deffn -Now that the vertex attributes have been created, all that's left is -to bind them all together in a vertex array. Vertex arrays associate -each vertex attribute with an attribute index on the GPU. The indices -that are chosen must correspond with the indices that the shader -(@pxref{Shaders}) expects for each attribute. +@deffn {Procedure} current-vertex-array +Return the current vertex array. +@end deffn -@example -(define vertex-array - (make-vertex-array #:indices indices - #:attributes `((0 . ,vertices) - (1 . ,texcoords)))) -@end example +@defvar g:vertex-array +Render state for vertex arrays (@pxref{Rendering Engine}.) +@end defvar -With the vertex array created, the GPU is now fully aware of how to -interpret the data that it has been given in the original buffer. -Actually rendering this square is left as an exercise to the reader. -See the @ref{Shaders} section and the @code{shader-apply} procedure in -@ref{Rendering Engine} for the remaining pieces of a successful draw -call. Additionally, consider reading the source code for sprites, -shapes, or particles to see GPU buffers in action. +@node Shaders +@subsection Shaders -Without further ado, the API reference: +Shaders are programs that the GPU can evaluate that allow the +programmer to completely customized the final output of a GPU draw +call. The @code{(chickadee graphics shader)} module provides an API for +building custom shaders. -@deffn {Procedure} make-buffer data [#:name "anonymous"] @ - [#:length] [#:offset 0] [#:stride 0] [#:target @code{vertex}] @ - [#:usage @code{static}] +Shaders are written in the OpenGL Shading Language, or GLSL for short. +Chickadee aspires to provide a domain specific language for writing +shaders in Scheme, but we are not there yet. -Upload @var{data}, a bytevector, to the GPU. By default, the entire -bytevector is uploaded. A subset of the data may be uploaded by -specifying the @var{offset}, the index of the first byte to be -uploaded, and @var{length}, the number of bytes to upload. +Shader programs consist of two components: A vertex shader and a +fragment shader. A vertex shader receives vertex data (position +coordinates, texture coordinates, normals, etc.) and transforms them +as desired, whereas a fragment shader controls the color of each +pixel. -If @var{data} is @code{#f}, allocate @var{length} bytes of fresh GPU -memory instead. +Sample vertex shader: -@var{target} and @var{usage} are hints that tell the GPU how the -buffer is intended to be used. +@example +@verbatim +#version 130 -@var{target} may be: +in vec2 position; +in vec2 tex; +out vec2 fragTex; +uniform mat4 mvp; -@itemize -@item @code{vertex} -Vertex attribute data. +void main(void) { + fragTex = tex; + gl_Position = mvp * vec4(position.xy, 0.0, 1.0); +} +@end verbatim +@end example -@item @code{index} -Index buffer data. +Sample fragment shader: -@end itemize +@example +@verbatim +#version 130 -@var{usage} may be: +in vec2 fragTex; +uniform sampler2D colorTexture; -@itemize -@item @code{static} -The buffer data will not be modified after creation. +void main (void) { + gl_FragColor = texture2D(colorTexture, fragTex); +} +@end verbatim +@end example -@item @code{stream} -The buffer data will be modified frequently. +This manual will not cover GLSL features and syntax as there is lots +of information already available about this topic. -@end itemize +One way to think about rendering with shaders, and the metaphor +Chickadee uses, is to think about it as a function call: The shader is +a function, and it is applied to some ``attributes'' (positional +arguments), and some ``uniforms'' (keyword arguments). -@var{name} is simply an arbitrary string for debugging purposes that -is never sent to the GPU. +@example +(define my-shader (load-shader "vert.glsl" "frag.glsl")) +(define vertices (make-vertex-array @dots{})) +(shader-apply my-shader vertices #:color red) +@end example + +@xref{Rendering Engine} for more details about the @code{shader-apply} +procedure. + +Shaders are incredibly powerful tools, and there's more information +about them than we could ever fit into this manual, so we highly +recommend searching the web for more information and examples. What +we can say, though, is how to use our API: + +@deffn {Procedure} strings->shader vertex-source fragment-source +Compile @var{vertex-source}, the GLSL code for the vertex shader, and +@var{fragment-source}, the GLSL code for the fragment shader, into a +GPU shader program. @end deffn -@deffn {Procedure} buffer? obj -Return @code{#t} if @var{obj} is a GPU buffer. +@deffn {Procedure} load-shader vertex-source-file fragment-source-file +Compile the GLSL source code within @var{vertex-source-file} and +@var{fragment-source-file} into a GPU shader program. @end deffn -@deffn {Procedure} index-buffer? buffer -Return @code{#t} if @var{buffer} is an index buffer. +@deffn {Procedure} make-shader vertex-port fragment-port +Read GLSL source from @var{vertex-port} and @var{fragment-port} and +compile them into a GPU shader program. @end deffn -@defvar null-buffer -Represents the absence of a buffer. +@deffn {Procedure} shader? obj +Return @code{#t} if @var{obj} is a shader. +@end deffn + +@defvar null-shader +Represents the absence shader program. @end defvar -@deffn {Procedure} buffer-name buffer -Return the name of @var{buffer}. +@deffn {Procedure} shader-uniform shader name +Return the metadata for the uniform @var{name} in @var{shader}. @end deffn -@deffn {Procedure} buffer-length buffer -Return the length of @var{buffer}. +@deffn {Procedure} shader-uniforms shader +Return a hash table of uniforms for @var{shader}. @end deffn -@deffn {Procedure} buffer-stride buffer -Return the amount of space, in bytes, between each element in -@var{buffer}. +@deffn {Procedure} shader-attributes shader +Return a hash table of attributes for @var{shader}. @end deffn -@deffn {Procedure} buffer-target buffer -Return the the intended usage of @var{buffer}, either @code{vertex} or -@code{index}. +@deffn {Procedure} shader-uniform-set! shader uniform value @end deffn -@deffn {Procedure} buffer-usage buffer -Return the intended usage of @var{buffer}, either @code{static} for -buffer data that will not change once sent to the GPU, or -@code{stream} for buffer data that will be frequently updated from the -client-side. +@subsubsection Attributes + +@deffn {Procedure} attribute? obj +Return @code{#t} if @var{obj} is an attribute. @end deffn -@deffn {Procedure} buffer-data buffer -Return a bytevector containing all the data within @var{buffer}. If -@var{buffer} has not been mapped (see @code{with-mapped-buffer}) then -this procedure will return @code{#f}. +@deffn {Procedure} attribute-name attribute +Return the variable name of @var{attribute}. @end deffn -@deffn {Syntax} with-mapped-buffer buffer body @dots{} -Evaluate @var{body} in the context of @var{buffer} having its data -synced from GPU memory to RAM. In this context, @code{buffer-data} -will return a bytevector of all the data stored in @var{buffer}. When -program execution exits this form, the data (including any -modifications) is synced back to the GPU. +@deffn {Procedure} attribute-location attribute +Return the binding location of @var{attribute}. +@end deffn -This form is useful for streaming buffers that need to update their -contents dynamically, such as a sprite batch. +@deffn {Procedure} attribute-type attribute +Return the data type of @var{attribute}. @end deffn -@deffn {Procedure} make-vertex-attribute #:buffer #:type @ - #:component-type #:length [#:offset @code{0}] [#:divisor @code{1}] @ - [#:name @code{"anonymous"}] +@subsubsection Uniforms -Return a new vertex attribute for @var{buffer} starting at byte index -@var{offset} of @var{length} elements, where each element is of -@var{type} and composed of @var{component-type} values. +@deffn {Procedure} uniform? obj +Return @code{#t} if @var{obj} is a uniform. +@end deffn -Valid values for @var{type} are: +@deffn {Procedure} uniform-name uniform +Return the variable name of @var{uniform}. +@end deffn -@itemize -@item @code{scalar} -single number +@deffn {Procedure} uniform-type uniform +Return the data type of @var{uniform}. +@end deffn -@item @code{vec2} -2D vector +@deffn {Procedure} uniform-value uniform +Return the current value of @var{uniform}. +@end deffn -@item @code{vec3} -3D vector +@subsubsection User-Defined Shader Types -@item @code{vec4} -4D vector +The shader examples in this manual thus far have only shown uniforms +defined using primitive types. However, GLSL shaders support +user-defined compound structs, such as this one: -@item @code{mat2} -2x2 matrix +@example +@verbatim +struct Light { + bool enabled; + int type; + vec3 position; + vec3 direction; + vec4 color; + float intensity; + float cutOff; +}; -@item @code{mat3} -3x3 matrix +uniform Light light; +@end verbatim +@end example -@item @code{mat4} -4x4 matrix -@end itemize +While @code{light} is declared as a single uniform in the shader code, +OpenGL translates this into @emph{seven} uniforms in this case: One +uniform each member of the @code{Light} struct. This poses a problem +for sending Scheme data to the GPU. How can compound Scheme data +translate into compound uniform data on the GPU? The answer is with +shader types. Shader types are a special kind of Guile struct that +provide a one-to-one mapping between a Scheme data structure and a +shader struct. -Valid values for @var{component-type} are: +Some example code will explain this concept best. Here is the Scheme +equivalent of the @code{Light} struct: -@itemize +@example +(define-shader-type + make-light + light? + (bool enabled light-enabled?) + (int type light-type) + (float-vec3 position light-position) + (float-vec3 direction light-direction) + (float-vec4 color light-color) + (float intensity light-intensity) + (float cut-off light-cut-off)) +@end example -@item @code{byte} -@item @code{unsigned-byte} -@item @code{short} -@item @code{unsigned-short} -@item @code{int} -@item @code{unsigned-int} -@item @code{float} -@item @code{double} +The macro @code{define-shader-type} closely resembles the familiar +@code{define-record-type} from SRFI-9, but with one notable +difference: Each struct field contains type information. The type +must be one of several primitive types (documented below) or another +shader type in the case of a nested structure. -@end itemize +It is important to note that the names of the shader type fields +@emph{must} match the names of the struct members in the GLSL code, +otherwise Chickadee will be unable to perform the proper translation. + +As of this writing, this interface is new and experimental. It +remains to be seen if this model is robust enough for all use-cases. + +Primitive data types: + +@defvar bool +Either @code{#t} or @code{#f}. +@end defvar + +@defvar int +An integer. +@end defvar + +@defvar unsigned-int +An unsigned integer. +@end defvar + +@defvar float +A floating point number. +@end defvar + +@defvar float-vec2 +A 2D vector (@pxref{Vectors}.) +@end defvar + +@defvar float-vec3 +A 3D vector (@pxref{Vectors}.) +@end defvar + +@defvar float-vec4 +A color (@pxref{Colors}) or rectangle (@pxref{Rectangles}.) +@end defvar + +@defvar mat3 +A 3x3 matrix (@pxref{Matrices}.) +@end defvar + +@defvar mat4 +A 4x4 matrix (@pxref{Matrices}.) +@end defvar + +@defvar sampler-2d +A texture (@pxref{Textures}.) +@end defvar + +@defvar sampler-cube +A cube map (@pxref{Textures}.) +@end defvar + +@defvar local-field +A special type that means that the data is for the client-side +(Scheme-side) only and should not be sent to the GPU. Any object may +be stored in a local field. +@end defvar + +@deffn {Syntax} define-shader-type constructor predicate @ + (field-type field-name [field-getter] [field-setter]) @dots{} + +Define a new shader data type called @var{}. + +Instances of this data type are created by calling the +@var{constructor} procedure. This procedure maps each field to a +keyword argument. A shader data type with the fields @code{foo}, +@code{bar}, and @code{baz} would have a constructor that accepts the +keyword arguments @code{#:foo}, @code{#:bar}, and @code{#:baz}. + +A procedure named @var{predicate} will test if an object is a +@var{} shader data type. + +Fields follow the format @code{(field-type field-name [field-getter] +[field-setter])}. @var{field-type} and @var{field-name} are required +for each field, but @var{field-getter} and @var{field-setter} are +optional. -@var{divisor} is only needed for instanced rendering applications (see -@code{shader-apply/instanced} in @ref{Rendering Engine}) and represents -how many instances each vertex element applies to. A divisor of 0 -means that a single element is used for every instance and is used for -the data being instanced. A divisor of 1 means that each element is -used for 1 instance. A divisor of 2 means that each element is used -for 2 instances, and so on. @end deffn -@deffn {Procedure} vertex-attribute? obj -Return @code{#t} if @var{obj} is a vertex attribute. +@deffn {Procedure} shader-data-type? obj +Return @code{#t} if @var{obj} is a shader data type object. @end deffn -@deffn {Procedure} vertex-attribute->buffer vertex-attribute -Return the buffer that @var{vertex-attribute} is using. +@node Framebuffers +@subsection Framebuffers + +A framebuffer is a chunk of memory that the GPU can render things +onto. By default, the framebuffer that is used for rendering is the +one belonging to the game window, but custom framebuffers can be used +as well. A common use-case for custom framebuffers is applying +post-processing effects: The entire scene is rendered to a +framebuffer, and then the contents of that framebuffer are applied to +a post-processing shader and rendered to the game window. The +post-processing shader could do any number of things: scaling, +antialiasing, motion blur, etc. + +@deffn {Procedure} make-framebuffer width height @ + [#:min-filter @code{linear}] @ + [#:mag-filter @code{linear}] @ + [#:wrap-s @code{repeat}] @ + [#:wrap-t @code{repeat}] + +Create a new framebuffer that is @var{width} pixels wide and @var{height} pixels high. + +@var{min-filter} and @var{mag-filter} determine the scaling algorithm +applied to the framebuffer when rendering. By default, linear scaling +is used in both cases. To perform no smoothing at all, use +@code{nearest} for simple nearest neighbor scaling. This is typically +the best choice for pixel art games. @end deffn -@deffn {Procedure} vertex-attribute-name vertex-attribute -Return the name of @var{vertex-attribute}. +@deffn {Procedure} framebuffer? obj +Return @code{#t} if @var{obj} is a framebuffer. @end deffn -@deffn {Procedure} vertex-attribute-offset vertex-attribute -Return the byte offset of @var{vertex-attribute}. +@deffn {Procedure} framebuffer-texture fb +Return the texture backing the framebuffer @var{fb}. @end deffn -@deffn {Procedure} vertex-attribute-type vertex-attribute -Return the data type of @var{vertex-attribute}. +@deffn {Procedure} framebuffer-viewport fb +Return the default viewport (@pxref{Viewports}) used by the +framebuffer @var{fb}. @end deffn -@deffn {Procedure} vertex-attribute-component-type vertex-attribute -Return the component data type of @var{vertex-attribute} +@deffn {Procedure} null-framebuffer +The default framebuffer. @end deffn -@deffn {Procedure} vertex-attribute-divisor vertex-attribute -Return the instance divisor for @var{vertex-attribute}. +@deffn {Procedure} current-framebuffer +Return the current framebuffer. @end deffn -@deffn {Syntax} with-mapped-vertex-attribute vertex-attribute body @dots{} +@defvar g:framebuffer +Render state for framebuffers (@pxref{Rendering Engine}.) +@end defvar -Evaluate @var{body} in the context of @var{vertex-attribute} having -its data synced from GPU memory to RAM. See @code{with-mapped-buffer} -for more information. +@node Viewports +@subsection Viewports + +A viewport represents a subset of the screen (or framebuffer). When +rendering a frame, the resulting image will only appear within that +viewport. These aren't often needed, and Chickadee's default viewport +occupies the entire screen, but there are certain situations where +they are useful. For example, a split-screen multiplayer game may +render to two different viewports, each occupying a different half of +the screen. For information about how to set the current viewport, +see @code{with-viewport} in @ref{Rendering Engine}. + +The @code{(chickadee graphics viewport)} module provides the following +API: + +@deffn {Procedure} make-viewport x y width height @ + [#:clear-color] [#:clear-flags] + +Create a viewport that covers an area of the window starting from +coordinates (@var{x}, @var{y}) and spanning @var{width} @code{x} +@var{height} pixels. Fill the viewport with @var{clear-color} when +clearing the screen. Clear the buffers denoted by the list of symbols +in @var{clear-flags}. + +Possible values for @var{clear-flags} are @var{color-buffer}, +@var{depth-buffer}, @var{accum-buffer}, and @var{stencil-buffer}. @end deffn -@deffn {Procedure} make-vertex-array #:indices #:attributes @ - [#:mode @code{triangles}] +@deffn {Procedure} viewport? obj +Return @code{#t} if @var{obj} is a viewport. +@end deffn -Return a new vertex array using the index data within the vertex -attributes @var{indices} and the vertex attribute data within -@var{attributes}. +@deffn {Procedure} viewport-x viewport +Return the left edge of @var{viewport}. +@end deffn -@var{attributes} is an alist mapping shader attribute indices to -vertex attributes: +@deffn {Procedure} viewport-y viewport +Return the bottom edge of @var{viewport}. +@end deffn -@example -`((1 . ,vertex-attribute-a) - (2 . ,vertex-attribute-b) - @dots{}) -@end example +@deffn {Procedure} viewport-width viewport +Return the width of @var{viewport}. +@end deffn -By default, the vertex array is interpreted as containing a series of -triangles. If another primtive type is desired, the @var{mode} -keyword argument may be overridden. The following values are -supported: +@deffn {Procedure} viewport-height viewport +Return the height of @var{viewport}. +@end deffn + +@deffn {Procedure} viewport-clear-color viewport +Return the clear color for @var{viewport}. +@end deffn + +@deffn {Procedure} viewport-clear-flags viewport +Return the list of clear flags for @var{viewport}. +@end deffn + +@deffn {Procedure} current-viewport +Return the current viewport. +@end deffn + +@defvar g:viewport +Render state for viewports (@pxref{Rendering Engine}.) +@end defvar + +@node Render Settings +@subsection Render Settings + +@subsubsection Blending + +Rendering a scene often involves drawing layers of objects that +overlap each other. Blending determines how two overlapping pixels +are combined in the final image that is rendered to the screen. The +@code{(chickadee graphics blend)} module provides a data type for +blending modes. + +Chickadee provides the following blend modes: + +@defvar blend:alpha +Blend pixels according to the values of their alpha channels. This is +the most commonly used blend mode. +@end defvar + +@defvar blend:replace +Overwrite the output pixel color with the color being drawn. +@end defvar + +@defvar blend:add +Add all pixel color values together. The more colors blended +together, the more white the final color becomes. +@end defvar + +@defvar blend:subtract +Subtract all pixel color values. The more colors blended together, +the more black the final color becomes. +@end defvar + +@defvar blend:multiply +@end defvar + +@defvar blend:darken +@end defvar + +@defvar blend:lighten +@end defvar + +@defvar blend:screen +@end defvar + +Custom blend modes can be created using the @code{make-blend-mode} procedure: + +@deffn {Procedure} make-blend-mode equation source-function destination-function +Return a new custom blend mode that applies @var{source-function} to +the source color, @var{destination-function} to the destination color, +and finally applies @var{equation} to the transformed +source/destination color values. These arguments are @emph{not} +procedures, but symbolic representations of the functions that OpenGL +supports. + +Valid values for @var{equation} are: @itemize -@item @code{points} -@item @code{lines} -@item @code{line-loop} -@item @code{line-strip} -@item @code{triangles} -@item @code{triangle-strip} -@item @code{triangle-fan} +@item @code{add} +@item @code{subtract} +@item @code{reverse-subtract} +@item @code{min} +@item @code{max} +@item @code{alpha-min} +@item @code{alpha-max} +@end itemize + +Valid values for @var{source-function} are: + +@itemize +@item @code{zero} +@item @code{one} +@item @code{destination-color} +@item @code{one-minus-destination-color} +@item @code{source-alpha-saturate} +@item @code{source-alpha} +@item @code{one-minus-source-alpha} +@item @code{destination-alpha} +@item @code{one-minus-destination-alpha} +@item @code{constant-color} +@item @code{one-minus-constant-color} +@item @code{constant-alpha} +@item @code{one-minus-constant-alpha} +@end itemize + +Valid values for @var{destination-function} are: + +@itemize +@item @code{zero} +@item @code{one} +@item @code{source-color} +@item @code{one-minus-source-color} +@item @code{source-alpha} +@item @code{one-minus-source-alpha} +@item @code{destination-alpha} +@item @code{one-minus-destination-alpha} +@item @code{constant-color} +@item @code{one-minus-constant-color} +@item @code{constant-alpha} +@item @code{one-minus-constant-alpha} @end itemize @end deffn -@defvar null-vertex-array -Represents the absence of a vertex array. +@subsubsection Polygon Modes and Culling + +The @code{(chickadee graphics polygon)} module provides access to the +@code{g:polygon-mode} and @code{g:cull-face-mode} render states. + +@defvar fill-polygon-mode +Completely fill in the polygon. This is the default mode. +@end defvar + +@defvar line-polygon-mode +Render only the edges of the polygon. Produces a wireframe. +@end defvar + +@defvar point-polygon-mode +Render only the vertex positions as points. @end defvar -@deffn {Procedure} vertex-array? obj -Return @code{#t} if @var{obj} is a vertex array. +@deffn {Procedure} make-polygon-mode front back +Return a new polygon mode that uses the method @var{front} for the +front face and @var{back} for the back face. The valid modes are +@code{fill}, @code{line}, and @var{point}. @end deffn -@deffn {Procedure} vertex-array-indices vertex-array -Return the buffer view containing index data for @var{vertex-array}. +@deffn {Procedure} polygon-mode? obj +Return @code{#t} if @var{obj} is a polygon mode. @end deffn -@deffn {Procedure} vertex-array-attributes vertex-array -Return the attribute index -> buffer view mapping of vertex attribute -data for @var{vertex-array}. +@deffn {Procedure} current-polygon-mode +Return the current polygon mode. @end deffn -@deffn {Procedure} vertex-array-mode vertex-array -Return the primitive rendering mode for @var{vertex-array}. -@end deffn +@defvar g:polygon-mode +Render state for polygon modes (@pxref{Rendering Engine}.) +@end defvar -@node Shaders -@subsection Shaders +@defvar no-cull-face-mode +Don't cull any faces. +@end defvar -Shaders are programs that the GPU can evaluate that allow the -programmer to completely customized the final output of a GPU draw -call. The @code{(chickadee graphics shader)} module provides an API for -building custom shaders. +@defvar back-cull-face-mode +Cull only back faces. +@end defvar -Shaders are written in the OpenGL Shading Language, or GLSL for short. -Chickadee aspires to provide a domain specific language for writing -shaders in Scheme, but we are not there yet. +@defvar front-cull-face-mode +Cull only front faces. +@end defvar -Shader programs consist of two components: A vertex shader and a -fragment shader. A vertex shader receives vertex data (position -coordinates, texture coordinates, normals, etc.) and transforms them -as desired, whereas a fragment shader controls the color of each -pixel. +@defvar front-and-back-cull-face-mode +Cull both front and back faces. +@end defvar -Sample vertex shader: +@deffn {Procedure} cull-face-mode? obj +Return @code{#t} if @var{obj} is a cull face mode. +@end deffn -@example -@verbatim -#version 130 +@deffn {Procedure} cull-face-mode-front? cull-face-mode +Return @code{#t} if @var{cull-face-mode} culls front faces. +@end deffn -in vec2 position; -in vec2 tex; -out vec2 fragTex; -uniform mat4 mvp; +@deffn {Procedure} cull-face-mode-back? cull-face-mode +Return @code{#t} if @var{cull-face-mode} culls back faces. +@end deffn -void main(void) { - fragTex = tex; - gl_Position = mvp * vec4(position.xy, 0.0, 1.0); -} -@end verbatim -@end example +@deffn {Procedure} current-cull-face-mode +Return the current cull face mode. +@end deffn -Sample fragment shader: +@defvar g:cull-face-mode +Render state for cull face modes (@pxref{Rendering Engine}.) +@end defvar -@example -@verbatim -#version 130 +@subsubsection Depth Testing -in vec2 fragTex; -uniform sampler2D colorTexture; +The @code{(chickadee graphics depth)} module provides access to the +@code{g:depth-test} render state. -void main (void) { - gl_FragColor = texture2D(colorTexture, fragTex); -} -@end verbatim -@end example +@deffn {Procedure} make-depth-test [#:write? #t] [#:function 'less-than] @ + [#:near 0.0] [#:far 1.0] -This manual will not cover GLSL features and syntax as there is lots -of information already available about this topic. +Return a new depth test object. If @var{write} is @code{#t}, the +depth buffer will be written to during a draw call. @var{near} and +@var{far} define the min/max Z values for which depth testing may +pass. -One way to think about rendering with shaders, and the metaphor -Chickadee uses, is to think about it as a function call: The shader is -a function, and it is applied to some ``attributes'' (positional -arguments), and some ``uniforms'' (keyword arguments). +@var{function} specifies how the depth value of pixel being drawn +compares to the depth value that is already in the depth buffer. When +this comparison is true, the depth test passes and the pixel is drawn. +When it fails, the pixel is discarded. -@example -(define my-shader (load-shader "vert.glsl" "frag.glsl")) -(define vertices (make-vertex-array @dots{})) -(shader-apply my-shader vertices #:color red) -@end example +The possible values of @var{function} are: -@xref{Rendering Engine} for more details about the @code{shader-apply} -procedure. +@itemize +@item @code{always} +@item @code{never} +@item @code{equal} +@item @code{not-equal} +@item @code{less-than} +@item @code{less-than-or-equal} +@item @code{greater-than} +@item @code{greater-than-or-equal} +@end itemize -Shaders are incredibly powerful tools, and there's more information -about them than we could ever fit into this manual, so we highly -recommend searching the web for more information and examples. What -we can say, though, is how to use our API: +@end deffn -@deffn {Procedure} strings->shader vertex-source fragment-source -Compile @var{vertex-source}, the GLSL code for the vertex shader, and -@var{fragment-source}, the GLSL code for the fragment shader, into a -GPU shader program. +@deffn {Procedure} depth-test? obj +Return @code{#t} when @var{obj} is a depth test object. @end deffn -@deffn {Procedure} load-shader vertex-source-file fragment-source-file -Compile the GLSL source code within @var{vertex-source-file} and -@var{fragment-source-file} into a GPU shader program. +@deffn {Procedure} depth-test-write? depth-test +Return @code{#t} when @var{depth-test} will write to the depth buffer. @end deffn -@deffn {Procedure} make-shader vertex-port fragment-port -Read GLSL source from @var{vertex-port} and @var{fragment-port} and -compile them into a GPU shader program. +@deffn {Procedure} depth-test-function depth-test +Return the comparison function of @var{depth-test}. @end deffn -@deffn {Procedure} shader? obj -Return @code{#t} if @var{obj} is a shader. +@deffn {Procedure} depth-test-near depth-test +Return the near Z value of @var{depth-test}. @end deffn -@defvar null-shader -Represents the absence shader program. -@end defvar +@deffn {Procedure} depth-test-far depth-test +Return the far Z value of @var{depth-test}. +@end deffn -@deffn {Procedure} shader-uniform shader name -Return the metadata for the uniform @var{name} in @var{shader}. +@deffn {Procedure} current-depth-test +Return the current depth test. @end deffn -@deffn {Procedure} shader-uniforms shader -Return a hash table of uniforms for @var{shader}. +@defvar g:depth-test +Render state for depth tests (@pxref{Rendering Engine}.) +@end defvar + +@subsubsection Stencil Testing + +The @code{(chickadee graphics stencil)} module provides access to the +@code{g:stencil-test} render state. + +@defvar default-stencil-test +A stencil test that always passes. +@end defvar + +@deffn {Procedure} make-stencil-test [#:mask #xFF] [#:function always] @ + [#:function-mask #xFF] [#:reference 0] [#:on-fail keep] @ + [#:on-depth-fail keep] [#:on-pass keep] [#:mask-front mask] @ + [#:mask-back mask] [#:function-mask-front function-mask] @ + [#:function-mask-back function-mask] [#:refrence-front reference] @ + [#:reference-back reference] [#:on-fail-front on-fail] @ + [#:on-fail-back on-fail] [#:on-depth-fail-front on-depth-fail] @ + [#:on-depth-fail-back on-depth-fail] [#:on-pass-front on-pass] @ + [#:on-pass-back on-pass] + +Return a new stencil test object. Different configurations can be +used for the front and back faces by using the arguments that end in +``front'' or ``back''. + +Valid values for @var{on-pass}, @var{on-fail}, and @var{on-depth-fail} +are: + +@itemize +@item @code{zero} +@item @code{keep} +@item @code{replace} +@item @code{increment} +@item @code{increment-wrap} +@item @code{decrement} +@item @code{decrement-wrap} +@item @code{invert} +@end itemize + +Valid values for @var{function} are: + +@itemize +@item @code{always} +@item @code{never} +@item @code{equal} +@item @code{not-equal} +@item @code{less-than} +@item @code{less-than-or-equal} +@item @code{greater-than} +@item @code{greater-than-or-equal} +@end itemize + @end deffn -@deffn {Procedure} shader-attributes shader -Return a hash table of attributes for @var{shader}. +@deffn {Procedure} stencil-test? obj +Return @code{#t} when @var{obj} is a stencil test object. @end deffn -@deffn {Procedure} shader-uniform-set! shader uniform value +@deffn {Procedure} stencil-test-mask-front stencil-test +Return the front mask of @var{stencil-test}. @end deffn -@subsubsection Attributes +@deffn {Procedure} stencil-test-mask-back stencil-test +Return the back mask of @var{stencil-test}. +@end deffn -@deffn {Procedure} attribute? obj -Return @code{#t} if @var{obj} is an attribute. +@deffn {Procedure} stencil-test-function-front stencil-test +Return the front function of @var{stencil-test}. @end deffn -@deffn {Procedure} attribute-name attribute -Return the variable name of @var{attribute}. +@deffn {Procedure} stencil-test-function-back stencil-test +Return the back function of @var{stencil-test}. @end deffn -@deffn {Procedure} attribute-location attribute -Return the binding location of @var{attribute}. +@deffn {Procedure} stencil-test-function-mask-front stencil-test +Return the front function-mask of @var{stencil-test}. @end deffn -@deffn {Procedure} attribute-type attribute -Return the data type of @var{attribute}. +@deffn {Procedure} stencil-test-function-mask-back stencil-test +Return the back function-mask of @var{stencil-test}. @end deffn -@subsubsection Uniforms +@deffn {Procedure} stencil-test-reference-front stencil-test +Return the front reference value of @var{stencil-test}. +@end deffn -@deffn {Procedure} uniform? obj -Return @code{#t} if @var{obj} is a uniform. +@deffn {Procedure} stencil-test-reference-back stencil-test +Return the back reference value of @var{stencil-test}. @end deffn -@deffn {Procedure} uniform-name uniform -Return the variable name of @var{uniform}. +@deffn {Procedure} stencil-test-on-fail-front stencil-test +Return the front failure action of @var{stencil-test}. @end deffn -@deffn {Procedure} uniform-type uniform -Return the data type of @var{uniform}. +@deffn {Procedure} stencil-test-on-fail-back stencil-test +Return the back failure action of @var{stencil-test}. @end deffn -@deffn {Procedure} uniform-value uniform -Return the current value of @var{uniform}. +@deffn {Procedure} stencil-test-on-depth-fail-front stencil-test +Return the front depth test failure action of @var{stencil-test}. @end deffn -@subsubsection User-Defined Shader Types +@deffn {Procedure} stencil-test-on-depth-fail-back stencil-test +Return the back depth test failure action of @var{stencil-test}. +@end deffn -The shader examples in this manual thus far have only shown uniforms -defined using primitive types. However, GLSL shaders support -user-defined compound structs, such as this one: +@deffn {Procedure} stencil-test-on-pass-front stencil-test +Return the front pass action of @var{stencil-test}. +@end deffn -@example -@verbatim -struct DirectionalLight { - vec3 direction; - vec3 ambient; - vec3 diffuse; - vec3 specular; -}; +@deffn {Procedure} stencil-test-on-pass-back stencil-test +Return the back pass action of @var{stencil-test}. +@end deffn -uniform DirectionalLight light; -@end verbatim -@end example +@deffn {Procedure} current-stencil-test +Return the current stencil test. +@end deffn -While @code{light} is declared as a single uniform in the shader code, -OpenGL translates this into @emph{four} uniforms in this case: One -uniform each member of the @code{DirectionalLight} struct. This poses -a problem for sending Scheme data to the GPU. How can compound Scheme -data translate into compound uniform data on the GPU? The answer is -with shader types. Shader types are a special kind of Guile struct -that provide a one-to-one mapping between a Scheme data structure and -a shader struct. +@defvar g:stencil-test +Render state for stencil testing (@pxref{Rendering Engine}.) +@end defvar -Some example code will explain this concept best. Here is the Scheme -equivalent of the @code{DirectionalLight} struct: +@subsubsection Multisample Antialiasing -@example -(define-shader-type - make-directional-light - directional-light? - (float-vec3 direction directional-light-direction) - (float-vec3 ambient directional-light-ambient) - (float-vec3 diffuse directional-light-diffuse) - (float-vec3 specular directional-light-specular) - (float shininess directional-light-shininess)) -@end example +Multisample antialiasing is a feature supported by many, but not all, +graphics cards. It is a nice easy way to improve the final frame that +the user sees, particularly in 3D scenes. The @code{(chickadee +graphics multisample)} module provides access to the +@code{g:multisample?} render state. -The macro @code{define-shader-type} closely resembles the familiar -@code{define-record-type} from SRFI-9, but with one notable -difference: Each struct field contains type information. The type -must be one of several primitive types (documented below) or another -shader type in the case of a nested structure. +@deffn {Procedure} current-multisample +Return @code{#t} if multisampling is enabled. +@end deffn -It is important to note that the names of the shader type fields -@emph{must} match the names of the struct members in the GLSL code, -otherwise Chickadee will be unable to perform the proper translation. +@defvar g:multisample? +Render state for multisampling (@pxref{Rendering Engine}.) +@end defvar -As of this writing, this interface is new and experimental. It -remains to be seen if this model is robust enough for all use-cases. +@node Rendering Engine +@subsection Rendering Engine -Primitive data types: +The @code{(chickadee graphics engine)} module provides a Scheme +abstraction to the state of the GPU driver. When the Chickadee game +loop launches, it takes care to initialize the engine. All draw calls +and state changes happen within the context of this engine. -@defvar bool -Either @code{#t} or @code{#f}. -@end defvar +Performing a custom draw call could look something like this: -@defvar int -An integer. -@end defvar +@example +(with-graphics-state ((g:blend-mode blend:alpha) + (g:texture-0 my-texture)) + (shader-apply my-shader #:foo 1)) +@end example -@defvar unsigned-int -An unsigned integer. -@end defvar +@subsubsection Render States -@defvar float -A floating point number. -@end defvar +Render states represent individual state values on the GPU. For +example, the current shader. As a naming convention, Chickadee +prefixes variables containing render states with @code{g:}. Render +states can be manipulated using the @code{with-graphics-state} macro. -@defvar float-vec2 -A 2D vector (@pxref{Vectors}.) -@end defvar +@deffn {Syntax} with-graphics-state ((name value) @dots{}) body @dots{} +Evaluate @var{body} with render states defined by @var{name} changed +to @var{value}. The render states are restored to their previous +values afterwards. +@end deffn -@defvar float-vec3 -A 3D vector (@pxref{Vectors}.) -@end defvar +One additional piece of state that the rendering engine has, that is +not part of the GPU state, is the current projection matrix: -@defvar float-vec4 -A color. -@end defvar +@deffn {Procedure} current-projection +Return the currently bound projection matrix (@pxref{Matrices}). +@end deffn -@defvar mat4 -A matrix (@pxref{Matrices}.) -@end defvar +@deffn {Syntax} with-projection projection body @dots{} +Evaluate @var{body} with the current projection matrix bound to +@var{projection} (@pxref{Matrices}). +@end deffn -@defvar sampler-2d -A texture (@pxref{Textures}.) -@end defvar +@subsubsection Rendering -@defvar local-field -A special type that means that the data is for the client-side -(Scheme-side) only and should not be sent to the GPU. Any object may -be stored in a local field. -@end defvar +Chickadee likens a GPU draw call to a Scheme procedure call. A shader +(@pxref{Shaders}) is like a procedure for the GPU to apply. Shaders +are passed arguments: The positional arguments are vertex array +attributes (@pxref{Buffers}) and the keyword arguments correspond to +the shader's uniform variables. Scheme uses @code{apply} to call a +procedure, so Chickadee uses @code{shader-apply} to call a shader. -@deffn {Syntax} define-shader-type constructor predicate @ - (field-type field-name [field-getter] [field-setter]) @dots{} +@deffn {Syntax} shader-apply shader vertex-array @ + [#:uniform-key uniform-value @dots{}] +@deffnx {Syntax} shader-apply* shader vertex-array count @ + [#:uniform-key uniform-value @dots{}] -Define a new shader data type called @var{}. +Render @var{vertex-array} using @var{shader} with the uniform values +specified in the following keyword arguments. -Instances of this data type are created by calling the -@var{constructor} procedure. This procedure maps each field to a -keyword argument. A shader data type with the fields @code{foo}, -@code{bar}, and @code{baz} would have a constructor that accepts the -keyword arguments @code{#:foo}, @code{#:bar}, and @code{#:baz}. +While @code{shader-apply} will draw every vertex in @var{vertex-array}, +@code{shader-apply*} will only draw @var{count} vertices. +@end deffn -A procedure named @var{predicate} will test if an object is a -@var{} shader data type. +@deffn {Syntax} shader-apply/instanced shader vertex-array @ + n [#:uniform-key uniform-value @dots{}] +@deffnx {Syntax} shader-apply/instanced shader vertex-array @ + count n [#:uniform-key uniform-value @dots{}] -Fields follow the format @code{(field-type field-name [field-getter] -[field-setter])}. @var{field-type} and @var{field-name} are required -for each field, but @var{field-getter} and @var{field-setter} are -optional. +Render @var{vertex-array} @var{n} times using @var{shader} with the +uniform values specified in the following keyword arguments. -@end deffn +Instanced rendering is very beneficial for rendering the same object +many times with only small differences for each one. For example, the +particle effects described in @ref{Particles} use instanced rendering. -@deffn {Procedure} shader-data-type? obj -Return @code{#t} if @var{obj} is a shader data type object. +While @code{shader-apply/instanced} will draw every vertex in +@var{vertex-array}, @code{shader-apply*} will only draw @var{count} +vertices. @end deffn @node Audio @@ -4317,15 +4621,9 @@ in the load hook (or anywhere else once the game loop is running) and play it! @example -(use-modules (chickadee audio)) - -(define audio #f) - -(define (load) - (set! audio (load-audio "neat-sound-effect.wav")) - (audio-play audio)) +(define sample (load-audio "neat-sound-effect.wav")) -(run-game #:load load) +(audio-play sample) @end example For more advanced usage, check out the full API reference in the diff --git a/doc/chickadee.texi b/doc/chickadee.texi index a65af9e..0585688 100644 --- a/doc/chickadee.texi +++ b/doc/chickadee.texi @@ -4,7 +4,7 @@ @settitle The Chickadee Game Toolkit @c %**end of header @copying -Copyright @copyright{} 2017-2020 David Thompson @email{davet@@gnu.org} +Copyright @copyright{} 2017-2021 David Thompson @email{davet@@gnu.org} @quotation Permission is granted to copy, distribute and/or modify this document @@ -53,8 +53,8 @@ The document was typeset with * Installation:: Installing Chickadee. * Getting Started:: Writing your first Chickadee program. * Command Line Interface:: Run Chickadee programs from the terminal. +* Live Coding:: Tips for building games from the REPL. * API Reference:: Chickadee API reference. - * Copying This Manual:: The GNU Free Documentation License and you! * Index:: @end menu @@ -93,7 +93,6 @@ Chickadee depends on the following packages: @item libvorbisfile @end itemize - @node Getting Started @chapter Getting Started @@ -271,11 +270,57 @@ after each change. @item --repl-server[=@var{port}] Launch a REPL server on port @var{port}, or 37146 by default. -Especially useful when paired with the -@url{https://www.nongnu.org/geiser/, Geiser} extension for Emacs. + +@command{telnet localhost 37146} (or whatever port number was given) +will do the trick, but using the @uref{https://www.nongnu.org/geiser/, +Geiser} extension for Emacs is by far the best way to develop at the +REPL with Guile. Use @code{M-x connect-to-guile} to connect to the +REPL server. @end table +@node Live Coding +@chapter Live Coding + +One of the biggest appeals of any Lisp dialect is the ability to use +the ``read-eval-print loop'' (REPL for short) to build programs +iteratively and interactively while the program is running. However, +programs that run in an event loop and respond to user input (such as +a game using the Chickadee library!) require special care for this +workflow to be pleasant. + +If you are using the @command{chickadee play} command to run your +game, then the @code{--repl} or @code{--repl-server} arguments are all +you need to get a live coding environment running. + +If, however, you are using @code{run-game} to start the game loop then +it's still fairly easy to hook up a special kind of REPL by yourself. + +First, create a cooperative REPL server (It's important to use Guile's +cooperative REPL server instead of the standard REPL server in +@code{(system repl server)} to avoid thread synchronization issues). +Then, in the game loop's update procedure, call +@code{poll-coop-repl-server} and pass the REPL object. Here is a +template to follow: + +@example +(use-modules (chickadee) + (system repl coop-server)) + +(define repl (spawn-coop-repl-server)) + +(define (update dt) + (poll-coop-repl-server repl) + ...) + +(run-game #:update update ...) +@end example + +To use the REPL, connect to it via port 37146. Telnet will do the +trick, but using the @uref{https://www.nongnu.org/geiser/, Geiser} +extension for Emacs is by far the best way to develop at the REPL with +Guile. Use @code{M-x connect-to-guile} to connect to the REPL server. + @node API Reference @chapter API Reference -- cgit v1.2.3