Skip to content

Commit

Permalink
Move palette alpha processing to the shader
Browse files Browse the repository at this point in the history
  • Loading branch information
ultraq committed Jun 2, 2024
1 parent 50c4542 commit 55561c7
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
package nz.net.ultraq.redhorizon.classic.filetypes

import nz.net.ultraq.redhorizon.filetypes.FileExtensions
import nz.net.ultraq.redhorizon.filetypes.Palette
import static nz.net.ultraq.redhorizon.filetypes.ColourFormat.FORMAT_RGB
import static nz.net.ultraq.redhorizon.filetypes.ColourFormat.FORMAT_RGBA

/**
* Implementation of the PAL file, which is an array of 256 colours (in VGA
Expand Down Expand Up @@ -47,24 +45,4 @@ class PalFile extends VgaPalette {

return "PAL file, 256 colour VGA palette (6 bits per pixel)"
}

/**
* Return a modification of this palette with the given alpha mask for a new
* 32-bit colour palette.
*
* @return
*/
Palette withAlphaMask() {

def paletteWithAlpha = new byte[size][FORMAT_RGBA.value]
size.times { i ->
def colour = this[i]
def colourWithAlpha =
i === 0 ? [0, 0, 0, 0] :
i === 4 ? [0, 0, 0, 127] :
[colour[0], colour[1], colour[2], 255]
paletteWithAlpha[i] = colourWithAlpha
}
return new Palette(size, FORMAT_RGBA, paletteWithAlpha)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class Map extends Node<Map> {

// TODO: Include more palettes in the project, or load these via the resource manager
palette = getResourceAsStream("ra-${theater.label.toLowerCase()}.pal").withBufferedStream { inputStream ->
return new PalFile(inputStream).withAlphaMask()
return new PalFile(inputStream)
}

var mapXY = new Vector2f(mapSection.x(), mapSection.y())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ out vec4 fragmentColour;

uniform sampler2D indexTexture;
uniform sampler2D paletteTexture;
uniform int[256] factionMap;
uniform int[256] adjustmentMap;
uniform sampler2D alphaMask;

/**
* Fragment shader main function, emits the fragment colour for a paletted
Expand All @@ -16,10 +17,16 @@ uniform int[256] factionMap;
*/
void main() {

// Index sampled from the texture, then run through the faction colour
// adjustment map, then used to pull a colour from the palette
// The final colour is obtained from multiple passes:
// - index value sampled from the texture
// - run through an adjustment map (eg: for different faction colours)
// - a colour is then pulled from the palette
// - where an alpha mask is applied
// - (and then the usual step of applying the vertex colouring)
float index = texture(indexTexture, v_textureUVs).x;
index = float(factionMap[int(index * 256)]) / 256;
fragmentColour = texture(paletteTexture, vec2(index, 0));
fragmentColour *= v_vertexColour;
index = float(adjustmentMap[int(index * 256)]) / 256;
vec4 colour = texture(paletteTexture, vec2(index, 0));
colour *= texture(alphaMask, vec2(index, 0));

fragmentColour = colour * v_vertexColour;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ package nz.net.ultraq.redhorizon.classic.shaders

import nz.net.ultraq.redhorizon.classic.Faction
import nz.net.ultraq.redhorizon.engine.graphics.Attribute
import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRenderer
import nz.net.ultraq.redhorizon.engine.graphics.Shader.ShaderLifecycle
import nz.net.ultraq.redhorizon.engine.graphics.ShaderConfig
import nz.net.ultraq.redhorizon.engine.graphics.Texture
import nz.net.ultraq.redhorizon.filetypes.ColourFormat

import groovy.transform.Memoized
import java.nio.ByteBuffer

/**
* A 2D sprite shader for palette-based sprites.
Expand All @@ -31,21 +36,52 @@ class PalettedSpriteShader extends ShaderConfig {

private static final int[] IDENTITY_MAP = 0..255

private static Texture alphaMask

PalettedSpriteShader() {

super(
'PalettedSprite',
'nz/net/ultraq/redhorizon/classic/shaders/PalettedSprite.vert.glsl',
'nz/net/ultraq/redhorizon/classic/shaders/PalettedSprite.frag.glsl',
[Attribute.POSITION, Attribute.COLOUR, Attribute.TEXTURE_UVS],
{ shader, material, window ->
[{ shader, material, window ->
shader.setUniformTexture('indexTexture', 0, material.texture)
},
{ shader, material, window ->
shader.setUniformTexture('paletteTexture', 1, material.palette)
},
{ shader, material, window ->
shader.setUniform('factionMap', buildAdjustmentMap(material.faction ?: Faction.GOLD))
{ shader, material, window ->
shader.setUniformTexture('paletteTexture', 1, material.palette)
},
{ shader, material, window ->
shader.setUniform('adjustmentMap', buildAdjustmentMap(material.faction ?: Faction.GOLD))
},
{ shader, material, window ->
shader.setUniformTexture('alphaMask', 2, alphaMask)
}],
new ShaderLifecycle() {

@Override
void delete(GraphicsRenderer renderer) {
renderer.delete(alphaMask)
}

@Override
void init(GraphicsRenderer renderer) {

// Hard-coded alpha values for the C&C games
var alphaMaskData = ByteBuffer.allocateNative(1024)
(0..255).each { i ->
alphaMaskData.put(
switch (i) {
case 0 -> new byte[]{ 0, 0, 0, 0 }
case 4 -> new byte[]{ 0, 0, 0, 0x7f }
default -> new byte[]{ 0xff, 0xff, 0xff, 0xff }
}
)
}
alphaMaskData.flip()

alphaMask = renderer.createTexture(256, 1, ColourFormat.FORMAT_RGBA, alphaMaskData)
}
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,29 +73,18 @@ class AudioSystem extends EngineSystem implements AudioRequests {
}

/**
* Run through and complete any registered deletions, returning whether or not
* there were items to process.
* Run through all of the queued requests for the creation and deletion of
* graphics resources.
*/
private boolean processDeletions(AudioRenderer renderer) {
private void processRequests(AudioRenderer renderer) {

if (deletionRequests) {
deletionRequests.drain().each { deletionRequest ->
def (resource, future) = deletionRequest
renderer.delete(resource)
future.complete(null)
}
return true
}
return false
}

/**
* Run through all of the queued requests for the creation and deletion of
* graphics resources.
*/
private void processRequests(AudioRenderer renderer) {

processDeletions(renderer)

if (creationRequests) {
creationRequests.drain().each { creationRequest ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,7 @@ class OpenALRenderer implements AudioRenderer {
@Override
String toString() {

return """
OpenAL audio renderer
- Vendor: ${alGetString(AL_VENDOR)}
- Device name: ${alGetString(AL_RENDERER)}
- OpenAL version: ${alGetString(AL_VERSION)}
""".stripIndent()
return "OpenAL device: ${alGetString(AL_VENDOR)}, ${alGetString(AL_RENDERER)}, OpenAL ${alGetString(AL_VERSION)}"
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package nz.net.ultraq.redhorizon.engine.graphics

import nz.net.ultraq.redhorizon.engine.geometry.Dimension
import nz.net.ultraq.redhorizon.engine.graphics.Shader.ShaderLifecycle
import nz.net.ultraq.redhorizon.events.EventTarget
import nz.net.ultraq.redhorizon.filetypes.ColourFormat

Expand Down Expand Up @@ -72,15 +73,17 @@ interface GraphicsRenderer extends AutoCloseable, EventTarget {
*/
default Shader createShader(ShaderConfig config) {

return createShader(config.name, config.vertexShaderSource, config.fragmentShaderSource, config.attributes, config.uniforms)
return createShader(config.name, config.vertexShaderSource, config.fragmentShaderSource, config.attributes,
config.uniforms, config.lifecycle)
}

/**
* Create a new shader program from a pair of vertex and fragment shader
* scripts, or return the existing shader program if one has already been
* created with the given name.
*/
Shader createShader(String name, String vertexShaderSource, String fragmentShaderSource, Attribute[] attributes, Uniform[] uniforms)
Shader createShader(String name, String vertexShaderSource, String fragmentShaderSource, Attribute[] attributes,
Uniform[] uniforms, ShaderLifecycle lifecycle)

/**
* Create a mesh to represent a surface onto which a texture will go. This is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,29 +120,18 @@ class GraphicsSystem extends EngineSystem implements GraphicsRequests {
}

/**
* Run through and complete any registered deletions, returning whether or not
* there were items to process.
* Run through all of the queued requests for the creation and deletion of
* graphics resources.
*/
private boolean processDeletions(GraphicsRenderer renderer) {
void processRequests(GraphicsRenderer renderer) {

if (deletionRequests) {
deletionRequests.drain().each { deletionRequest ->
def (resource, future) = deletionRequest
renderer.delete(resource)
future.complete(null)
}
return true
}
return false
}

/**
* Run through all of the queued requests for the creation and deletion of
* graphics resources.
*/
void processRequests(GraphicsRenderer renderer) {

processDeletions(renderer)

if (creationRequests) {
creationRequests.drain().each { creationRequest ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ abstract class Shader implements GraphicsResource {
final String name
final Attribute[] attributes
final Uniform[] uniforms
final ShaderLifecycle lifecycle

/**
* Update a shader's uniforms using the given context.
Expand Down Expand Up @@ -99,4 +100,22 @@ abstract class Shader implements GraphicsResource {
* Enable the use of this shader for the next rendering commands.
*/
abstract void use()

/**
* Additional operations to perform on shader init/delete.
*/
interface ShaderLifecycle {

/**
* Called when the shader program is being deleted, use for freeing up any
* shader resources.
*/
void delete(GraphicsRenderer renderer)

/**
* Called when the shader program is created, use for creating any
* additional shader resources.
*/
void init(GraphicsRenderer renderer)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package nz.net.ultraq.redhorizon.engine.graphics

import nz.net.ultraq.redhorizon.engine.graphics.Shader.ShaderLifecycle

/**
* The configuration for a shader program, can be used to build shaders with the
* renderer.
Expand All @@ -29,17 +31,28 @@ class ShaderConfig {
final String fragmentShaderSource
final Attribute[] attributes
final Uniform[] uniforms
final ShaderLifecycle lifecycle

/**
* Constructor, create a shader config for building a shader program later.
*/
ShaderConfig(String name, String vertexShaderResourcePath, String fragmentShaderResourcePath, List<Attribute> attributes, Uniform... uniforms) {

this(name, vertexShaderResourcePath, fragmentShaderResourcePath, attributes, uniforms as List<Uniform>, null)
}

/**
* Constructor, create a shader config with an optional initialization
* closure to build any resources the shader needs.
*/
ShaderConfig(String name, String vertexShaderResourcePath, String fragmentShaderResourcePath, List<Attribute> attributes,
List<Uniform> uniforms, ShaderLifecycle lifecycle) {

this.name = name
// TODO: I think these need to use closeable methods as getText() won't be doing that for us right?
this.vertexShaderSource = getResourceAsStream(vertexShaderResourcePath).text
this.fragmentShaderSource = getResourceAsStream(fragmentShaderResourcePath).text
this.vertexShaderSource = getResourceAsStream(vertexShaderResourcePath).withCloseable { it.text }
this.fragmentShaderSource = getResourceAsStream(fragmentShaderResourcePath).withCloseable { it.text }
this.attributes = attributes
this.uniforms = uniforms
this.lifecycle = lifecycle
}
}
Loading

0 comments on commit 55561c7

Please sign in to comment.