Skip to content

Commit

Permalink
Add tracking and ability to clean up ShaderPrograms.
Browse files Browse the repository at this point in the history
Also add expired VAO clean up to runtime cleanup code in ContextGarbageCollector.
  • Loading branch information
Renanse committed May 27, 2024
1 parent 77c16ab commit 978b80f
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public interface IShaderUtils {
* Attempts to delete a vertex array associated with this mesh data that is relevant to the current
* RenderContext.
*
* @param ids
* @param data
*/
void deleteVertexArray(MeshData data);

Expand All @@ -71,4 +71,10 @@ public interface IShaderUtils {
*/
void deleteVertexArrays(Collection<Integer> ids);

/**
* Attempts to delete shader programs with the given ids. Ignores null ids or ids < 1.
*
* @param ids
*/
void deleteShaderPrograms(List<Integer> ids);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,14 @@
import java.io.*;
import java.lang.ref.ReferenceQueue;
import java.nio.Buffer;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

import com.ardor3d.buffer.AbstractBufferData;
import com.ardor3d.light.LightManager;
import com.ardor3d.renderer.ContextManager;
import com.ardor3d.renderer.RenderContext;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.RendererCallable;
import com.ardor3d.renderer.material.uniform.Ardor3dStateProperty;
import com.ardor3d.renderer.material.uniform.UniformRef;
import com.ardor3d.renderer.material.uniform.UniformType;
Expand All @@ -33,12 +30,18 @@
import com.ardor3d.scenegraph.SceneIndexer;
import com.ardor3d.util.Ardor3dException;
import com.ardor3d.util.Constants;
import com.ardor3d.util.GameTaskQueueManager;
import com.ardor3d.util.collection.Multimap;
import com.ardor3d.util.collection.SimpleMultimap;
import com.ardor3d.util.gc.ContextValueReference;

public class TechniquePass {
/** Name of this pass - optional, useful for debug, etc. */
protected String _name;

private static final Map<TechniquePass, Object> _identityCache = Collections.synchronizedMap(new WeakHashMap<>());
private static final Object STATIC_REF = new Object();

/** Our shaders, mapped by their type. */
protected Map<ShaderType, List<String>> _shaders = new EnumMap<>(ShaderType.class);

Expand All @@ -54,9 +57,29 @@ public class TechniquePass {

protected Map<UniformRef, Integer> _cachedLocations = new IdentityHashMap<>();

public TechniquePass() {
synchronized (_identityCache) {
_identityCache.put(this, STATIC_REF);
}
}

/**
* @param context the OpenGL context to get our id for.
* @return the program id of a shader program in the given context. If the program is not found in the given
* * context rep, 0 is returned.
*/
public int getProgramId(final RenderContext context) {
return getProgramIdByRef(context.getSharableContextRef());
}

/**
* @param contextRef the reference to a shared GL context to get our id for.
* @return the program id of a shader program in the given context. If the program is not found in the given
* context rep, 0 is returned.
*/
public int getProgramIdByRef(final RenderContext.RenderContextRef contextRef) {
if (_shaderIdCache != null) {
final Integer id = _shaderIdCache.getValue(context.getSharableContextRef());
final Integer id = _shaderIdCache.getValue(contextRef);
if (id != null) {
return id;
}
Expand All @@ -75,6 +98,187 @@ public void setProgramId(final RenderContext context, final int id) {
_shaderIdCache.put(context.getSharableContextRef(), id);
}

/**
* Clean all tracked Shader Programs from the hardware, using the given utility object to do the work immediately,
* if given. If not, we will delete in the next execution of the appropriate context's game task
* render queue.
*
* @param utils
* the util class to use. If null, execution will not occur immediately.
*/
public static void cleanAllPrograms(final IShaderUtils utils) {
final Multimap<RenderContext.RenderContextRef, Integer> idMap = new SimpleMultimap<>();

// gather up expired shader program ids... these don't exist in our cache
gatherGCdIds(idMap);

Set<TechniquePass> keySetCopy;
synchronized (_identityCache) {
keySetCopy = new HashSet<>(_identityCache.keySet());
}

// Walk through the cached items and delete those too.
for (final TechniquePass pass : keySetCopy) {
if (pass._shaderIdCache != null) {
if (Constants.useMultipleContexts) {
final Set<RenderContext.RenderContextRef> contextObjects = pass._shaderIdCache.getContextRefs();
for (final RenderContext.RenderContextRef o : contextObjects) {
// Add id to map
idMap.put(o, pass.getProgramIdByRef(o));
}
} else {
idMap.put(ContextManager.getCurrentContext().getSharableContextRef(), pass.getProgramIdByRef(null));
}
pass._shaderIdCache.clear();
}
}

// send to be deleted (perhaps on next render.)
handleProgramDelete(utils, idMap);
}

/**
* Clean all tracked Shader Programs associated with the given RenderContext from the hardware, using the given utility object to do the work immediately,
* if given. If not, we will delete in the next execution of the appropriate context's game task
* render queue.
*
* @param utils the util class to use. If null, execution will not occur immediately.
* @param context the context to clean programs for.
*/
public static void cleanAllPrograms(final IShaderUtils utils, final RenderContext context) {
final Multimap<RenderContext.RenderContextRef, Integer> idMap = new SimpleMultimap<>();

// gather up expired vbos... these don't exist in our cache
gatherGCdIds(idMap);

final RenderContext.RenderContextRef glRef = context.getSharableContextRef();

Set<TechniquePass> keySetCopy;
synchronized (_identityCache) {
keySetCopy = new HashSet<>(_identityCache.keySet());
}

// Walk through the cached items and delete those too.
for (final TechniquePass pass : keySetCopy) {
// only worry about buffers that have received ids.
if (pass._shaderIdCache != null) {
final Integer id = pass._shaderIdCache.removeValue(glRef);
if (id != null && id != 0) {
idMap.put(context.getSharableContextRef(), id);
}
}
}

// send to be deleted (perhaps on next render.)
handleProgramDelete(utils, idMap);
}

/**
* Clean tracked Shader Programs from the hardware for this TechniquePass, using the given utility object to do the work immediately,
* if given. If not, we will delete in the next execution of the appropriate context's game task
* render queue.
*
* @param utils the util class to use. If null, execution will not occur immediately.
*/
public void cleanProgram(final IShaderUtils utils) {
if (_shaderIdCache != null) {
final Multimap<RenderContext.RenderContextRef, Integer> idMap = new SimpleMultimap<>();

if (Constants.useMultipleContexts) {
final Set<RenderContext.RenderContextRef> contextObjects = _shaderIdCache.getContextRefs();
for (final RenderContext.RenderContextRef o : contextObjects) {
// Add id to map
idMap.put(o, getProgramIdByRef(o));
}
} else {
idMap.put(ContextManager.getCurrentContext().getSharableContextRef(), getProgramIdByRef(null));
}

// clear out the cache
_shaderIdCache.clear();

// send to be deleted (perhaps on next render.)
handleProgramDelete(utils, idMap);
}
}

/**
* Clean any tracked, expired Shader Programs from the hardware, using the given utility object to do the work immediately,
* if given. If not, we will delete in the next execution of the appropriate context's game task
* render queue.
* <p>
* A Shader Program is considered expired if it has been garbage collected by Java.
*
* @param utils the util class to use. If null, execution will not occur immediately.
*/
public static void cleanExpiredPrograms(final IShaderUtils utils) {
// gather up expired vbos...
final Multimap<RenderContext.RenderContextRef, Integer> idMap = gatherGCdIds(null);

if (idMap != null) {
// send to be deleted (perhaps on next render.)
handleProgramDelete(utils, idMap);
}
}

@SuppressWarnings("unchecked")
private static Multimap<RenderContext.RenderContextRef, Integer> gatherGCdIds(Multimap<RenderContext.RenderContextRef, Integer> store) {
// Pull all expired shader programs from ref queue and add to an id multimap.
ContextValueReference<TechniquePass, Integer> ref;
while ((ref = (ContextValueReference<TechniquePass, Integer>) _shaderRefQueue.poll()) != null) {
if (Constants.useMultipleContexts) {
final Set<RenderContext.RenderContextRef> renderRefs = ref.getContextRefs();
for (final RenderContext.RenderContextRef renderRef : renderRefs) {
// Add id to map
final Integer id = ref.getValue(renderRef);
if (id != null) {
if (store == null) { // lazy init
store = new SimpleMultimap<>();
}
store.put(renderRef, id);
}
}
} else {
final Integer id = ref.getValue(null);
if (id != null) {
if (store == null) { // lazy init
store = new SimpleMultimap<>();
}
store.put(ContextManager.getCurrentContext().getSharableContextRef(), id);
}
}
ref.clear();
}

return store;
}

private static void handleProgramDelete(final IShaderUtils utils, final Multimap<RenderContext.RenderContextRef, Integer> idMap) {
RenderContext.RenderContextRef currentSharableRef = null;
// Grab the current context, if any.
if (utils != null && ContextManager.getCurrentContext() != null) {
currentSharableRef = ContextManager.getCurrentContext().getSharableContextRef();
}
// For each affected context...
for (final RenderContext.RenderContextRef sharableRef : idMap.keySet()) {
// If we have a deleter and the context is current, immediately delete
if (utils != null && sharableRef.equals(currentSharableRef)) {
utils.deleteShaderPrograms(idMap.values(sharableRef));
}
// Otherwise, add a delete request to that context's render task queue.
else {
GameTaskQueueManager.getManager(ContextManager.getContextForSharableRef(sharableRef))
.render(new RendererCallable<Void>() {
@Override
public Void call() {
getRenderer().getShaderUtils().deleteShaderPrograms(idMap.values(sharableRef));
return null;
}
});
}
}
}

public void setName(final String name) { _name = name; }

public String getName() { return _name; }
Expand Down
10 changes: 10 additions & 0 deletions ardor3d-core/src/main/java/com/ardor3d/scenegraph/MeshData.java
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,16 @@ public void read(final InputCapsule capsule) throws IOException {
updatePrimitiveCounts();
}

public static void cleanExpiredVertexArrays(final IShaderUtils utils) {
// gather up expired vertex arrays...
final Multimap<RenderContextRef, Integer> idMap = gatherGCdIds(null);

if (idMap != null) {
// send to be deleted (perhaps on next render.)
handleVAODelete(utils, idMap);
}
}

public static void cleanAllVertexArrays(final IShaderUtils utils) {
final Multimap<RenderContextRef, Integer> idMap = new SimpleMultimap<>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2008-2024 Bird Dog Games, Inc.
*
* This file is part of Ardor3D.
*
* Ardor3D is free software: you can redistribute it and/or modify it
* under the terms of its license which may be found in the accompanying
* LICENSE file or at <https://git.io/fjRmv>.
*/

package com.ardor3d.scenegraph.visitor;

import com.ardor3d.renderer.material.IShaderUtils;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.Spatial;

/**
* <code>DeleteShaderProgramsVisitor</code> is a visitor that will clear all uploaded
* shader programs associated with RenderMaterials in a given part of the scene graph.
*/
public class DeleteShaderProgramsVisitor implements Visitor {
final IShaderUtils _utils;

public DeleteShaderProgramsVisitor(final IShaderUtils utils) {
_utils = utils;
}

@Override
public void visit(final Spatial spatial) {
if (spatial instanceof Mesh mesh) {
var material = mesh.getRenderMaterial();
if (material == null) return;
material.getTechniques().forEach(technique ->
technique.getPasses().forEach(pass ->
pass.cleanProgram(_utils)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import com.ardor3d.buffer.AbstractBufferData;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.material.TechniquePass;
import com.ardor3d.scenegraph.MeshData;

public class ContextGarbageCollector {
Expand All @@ -30,7 +31,11 @@ private ContextGarbageCollector() {}
*/
public static void doRuntimeCleanup(final Renderer renderer) {
TextureManager.cleanExpiredTextures(renderer.getTextureUtils(), null);
AbstractBufferData.cleanExpiredVBOs(renderer.getShaderUtils());

final var shaderUtils = renderer.getShaderUtils();
AbstractBufferData.cleanExpiredVBOs(shaderUtils);
MeshData.cleanExpiredVertexArrays(shaderUtils);
TechniquePass.cleanExpiredPrograms(shaderUtils);
}

/**
Expand All @@ -46,7 +51,10 @@ public static void doRuntimeCleanup(final Renderer renderer) {
*/
public static void doFinalCleanup(final Renderer renderer) {
TextureManager.cleanAllTextures(renderer.getTextureUtils(), null);
AbstractBufferData.cleanAllBuffers(renderer.getShaderUtils());
MeshData.cleanAllVertexArrays(renderer.getShaderUtils());

final var shaderUtils = renderer.getShaderUtils();
AbstractBufferData.cleanAllBuffers(shaderUtils);
MeshData.cleanAllVertexArrays(shaderUtils);
TechniquePass.cleanAllPrograms(shaderUtils);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -787,4 +787,12 @@ public void deleteVertexArrays(final Collection<Integer> ids) {
}
}

@Override
public void deleteShaderPrograms(final List<Integer> ids) {
for (final Integer i : ids) {
if (i != null && i != 0) {
GL20C.glDeleteProgram(i);
}
}
}
}

0 comments on commit 978b80f

Please sign in to comment.