Skip to content

Commit

Permalink
Parallel texture atlas uploading
Browse files Browse the repository at this point in the history
  • Loading branch information
buthed010203 committed Mar 16, 2024
1 parent 0ecc1dc commit 9a50087
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 31 deletions.
8 changes: 5 additions & 3 deletions arc-core/src/arc/assets/AssetLoadingTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,12 @@ private void removeDuplicates(Seq<AssetDescriptor> array){
boolean ordered = array.ordered;
array.ordered = true;
for(int i = 0; i < array.size; ++i){
final String fn = array.get(i).fileName;
final Class type = array.get(i).type;
AssetDescriptor<?> ai = array.get(i);
final String fn = ai.fileName;
final Class<?> type = ai.type;
for(int j = array.size - 1; j > i; --j){
if(type == array.get(j).type && fn.equals(array.get(j).fileName))
AssetDescriptor<?> aj = array.get(j);
if(type == aj.type && fn.equals(aj.fileName))
array.remove(j);
}
}
Expand Down
7 changes: 1 addition & 6 deletions arc-core/src/arc/assets/AssetManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -510,12 +510,7 @@ synchronized void injectDependencies(String parentAssetFilename, Seq<AssetDescri

private synchronized void injectDependency(String parentAssetFilename, AssetDescriptor dependendAssetDesc){
// add the asset as a dependency of the parent asset
Seq<String> dependencies = assetDependencies.get(parentAssetFilename);
if(dependencies == null){
dependencies = new Seq();
assetDependencies.put(parentAssetFilename, dependencies);
}
dependencies.add(dependendAssetDesc.fileName);
assetDependencies.get(parentAssetFilename, new Seq<>()).add(dependendAssetDesc.fileName);

// if the asset is already loaded, increase its reference count.
if(isLoaded(dependendAssetDesc.fileName)){
Expand Down
81 changes: 61 additions & 20 deletions arc-core/src/arc/assets/loaders/TextureAtlasLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,81 @@
import arc.graphics.g2d.TextureAtlas.*;
import arc.graphics.g2d.TextureAtlas.TextureAtlasData.*;
import arc.struct.*;
import arc.util.*;

import java.util.concurrent.*;

/**
* {@link AssetLoader} to load {@link TextureAtlas} instances. Passing a {@link TextureAtlasParameter} to
* {@link AssetManager#load(String, Class, AssetLoaderParameters)} allows to specify whether the atlas regions should be flipped
* on the y-axis or not.
* @author mzechner
*/
public class TextureAtlasLoader extends SynchronousAssetLoader<TextureAtlas, TextureAtlasLoader.TextureAtlasParameter>{
public class TextureAtlasLoader extends AsynchronousAssetLoader<TextureAtlas, TextureAtlasLoader.TextureAtlasParameter>{
TextureAtlasData data;
ExecutorCompletionService<TextureLoader> pool;
int numTasks;

public TextureAtlasLoader(FileHandleResolver resolver){
super(resolver);
}

@Override
public TextureAtlas load(AssetManager assetManager, String fileName, Fi file, TextureAtlasParameter parameter){
public void loadAsync(AssetManager manager, String fileName, Fi atlasFile, TextureAtlasParameter parameter){
Fi imgDir = atlasFile.parent();
data = new TextureAtlasData(atlasFile, imgDir, parameter != null && parameter.flip);

ExecutorService exec = null; // Used so we can shut it down in this method
Seq<AssetDescriptor> dependencies = new Seq<>();
numTasks = 0;
for(AtlasPage page : data.getPages()){
String pageFileName = page.textureFile.path().replaceAll("\\\\", "/");
if(!manager.isLoaded(pageFileName)){
TextureParameter params = new TextureParameter();
params.genMipMaps = page.useMipMaps;
params.minFilter = page.minFilter;
params.magFilter = page.magFilter;
if(manager.getLoader(Texture.class, pageFileName).getClass() == TextureLoader.class){ // We cannot trust whatever subclass a mod may use, we should fall back to vanilla behavior
if(pool == null) pool = new ExecutorCompletionService(exec = Threads.executor("Texture Atlas Loader"));
// A single TextureLoader instance cannot be used by multiple threads at once, create a new one for every page to be safe
TextureLoader pageLoader = new TextureLoader(manager.getFileHandleResolver());
numTasks++;
pool.submit(() -> pageLoader.loadAsync(manager, pageFileName, page.textureFile, params), pageLoader);
}else{ // Add dependencies so that vanilla behavior is used instead
dependencies.add(new AssetDescriptor(page.textureFile, Texture.class, params));
}
}
}
if(dependencies.any()) Reflect.invoke(manager, "injectDependencies", new Object[]{fileName, dependencies}, String.class, Seq.class); // Emulate vanilla behavior of getDependencies.
if(exec != null) exec.shutdown(); // shut down the executor but do not wait for it
}

@Override
public TextureAtlas loadSync(AssetManager manager, String fileName, Fi atlasFile, TextureAtlasParameter parameter){
if(pool != null){
long await = Time.nanos();
try {
for(int i = 0; i < numTasks; i++){ // Funnily enough, the longest part of the atlas loading process is just waiting for the first page to finish loadAsync
TextureLoader pageLoader = pool.take().get();
AtlasPage page = data.getPages().find(p -> p.textureFile.path().replaceAll("\\\\", "/").equals(pageLoader.info.filename));
TextureParameter params = new TextureParameter();
params.genMipMaps = page.useMipMaps;
params.minFilter = page.minFilter;
params.magFilter = page.magFilter;
// Run the sync portion
page.texture = pageLoader.loadSync(manager, pageLoader.info.filename, page.textureFile, params);
}
}catch(InterruptedException | ExecutionException e){
throw new ArcRuntimeException(e);
}
pool = null;
Log.debug("Awaited atlas pool for: @ms", Time.millisSinceNanos(await));
}

// If a mod has caused the vanilla behavior fallback, the page textures won't have been set by the block above
for(AtlasPage page : data.getPages()){
page.texture = assetManager.get(page.textureFile.path(), Texture.class);
if(page.texture != null) continue; // This page was already set by the block above
page.texture = manager.get(page.textureFile.path(), Texture.class);
}

TextureAtlas atlas = new TextureAtlas(data);
Expand All @@ -35,23 +92,7 @@ public TextureAtlas load(AssetManager assetManager, String fileName, Fi file, Te

@Override
public Seq<AssetDescriptor> getDependencies(String fileName, Fi atlasFile, TextureAtlasParameter parameter){
Fi imgDir = atlasFile.parent();

if(parameter != null){
data = new TextureAtlasData(atlasFile, imgDir, parameter.flip);
}else{
data = new TextureAtlasData(atlasFile, imgDir, false);
}

Seq<AssetDescriptor> dependencies = new Seq<>();
for(AtlasPage page : data.getPages()){
TextureParameter params = new TextureParameter();
params.genMipMaps = page.useMipMaps;
params.minFilter = page.minFilter;
params.magFilter = page.magFilter;
dependencies.add(new AssetDescriptor<>(page.textureFile, Texture.class, params));
}
return dependencies;
return null; // We will inject the dependencies if they're needed later
}

public static class TextureAtlasParameter extends AssetLoaderParameters<TextureAtlas>{
Expand Down
4 changes: 2 additions & 2 deletions arc-core/src/arc/assets/loaders/TextureLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void loadAsync(AssetManager manager, String fileName, Fi file, TexturePar
info.texture = parameter.texture;
}
if(!info.data.isPrepared()) info.data.prepare();
// Log.infoTag("Textures", "Async: " + fileName + " in " + Time.millisSinceNanos(start) + "ms");
Log.infoTag("Textures", "Async: " + fileName + " in " + Time.millisSinceNanos(start) + "ms");
}

@Override
Expand All @@ -52,7 +52,7 @@ public Texture loadSync(AssetManager manager, String fileName, Fi file, TextureP
}else{
long texS = Time.nanos();
texture = new Texture(info.data);
Log.debug("Made the @ texture in @", fileName, Time.millisSinceNanos(texS));
Log.debug("Made the @ texture in @ms", fileName, Time.millisSinceNanos(texS));
}
if(parameter != null){
texture.setFilter(parameter.minFilter, parameter.magFilter);
Expand Down

0 comments on commit 9a50087

Please sign in to comment.