Progress on mission 'async everything'
This commit is contained in:
parent
a41923fa12
commit
a858fa9159
@ -12,13 +12,15 @@ import org.jaudiotagger.audio.wav.WavTag;
|
||||
import org.jaudiotagger.tag.FieldKey;
|
||||
import org.jaudiotagger.tag.TagException;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.Preferences;
|
||||
import com.badlogic.gdx.files.FileHandle;
|
||||
import com.badlogic.gdx.graphics.Pixmap;
|
||||
import com.badlogic.gdx.graphics.PixmapIO;
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.badlogic.gdx.utils.Disposable;
|
||||
|
||||
public class SongInfo implements Disposable {
|
||||
public class MusicInfo implements Disposable {
|
||||
|
||||
private long durationInSeconds;
|
||||
private String songName;
|
||||
@ -26,16 +28,15 @@ public class SongInfo implements Disposable {
|
||||
private String author;
|
||||
private int previousTop;
|
||||
private int ratedDifficulty;
|
||||
private byte[] albumWorkBytes;
|
||||
private boolean invalidMusic;
|
||||
|
||||
private boolean containsInfo;
|
||||
|
||||
private FileHandle musicFile;
|
||||
private Preferences musicAnnotation;
|
||||
public SongInfo(FileHandle musicFile, Preferences musicData) {
|
||||
public MusicInfo(FileHandle musicFile, Preferences musicAnnotation) {
|
||||
this.musicFile = musicFile;
|
||||
this.musicAnnotation = musicData;
|
||||
this.musicAnnotation = musicAnnotation;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,7 +51,12 @@ public class SongInfo implements Disposable {
|
||||
durationInSeconds = mp3File.getAudioHeader().getTrackLength();
|
||||
|
||||
if (mp3File.getTag() != null && mp3File.getTag().getFirstArtwork() != null) {
|
||||
albumWorkBytes = mp3File.getTag().getFirstArtwork().getBinaryData();
|
||||
if (!Gdx.files.external("RhythmBullet/AlbumArt/" + musicFile.name() + ".png").exists()) {
|
||||
byte[] albumWorkBytes = mp3File.getTag().getFirstArtwork().getBinaryData();
|
||||
Pixmap pixmap = new Pixmap(albumWorkBytes, 0, albumWorkBytes.length);
|
||||
PixmapIO.writePNG(Gdx.files.external("RhythmBullet/AlbumArt/" + musicFile.name() + ".png"), pixmap);
|
||||
pixmap.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
songName = mp3File.getTag().getFirst(FieldKey.TITLE);
|
||||
@ -91,13 +97,17 @@ public class SongInfo implements Disposable {
|
||||
containsInfo = true;
|
||||
}
|
||||
|
||||
public void setupTexture(Texture defaultAlbumCover) {
|
||||
if (albumWorkBytes != null) {
|
||||
Pixmap albumCoverFromBytes = new Pixmap(albumWorkBytes, 0, albumWorkBytes.length);
|
||||
albumCover = new Texture(albumCoverFromBytes);
|
||||
albumCoverFromBytes.dispose();
|
||||
/**
|
||||
* The album art as a texture.
|
||||
* Can return null if contains info is false.
|
||||
* Not automatically disposed.
|
||||
* @return
|
||||
*/
|
||||
public Texture loadTexture() {
|
||||
if (Gdx.files.external("RhythmBullet/AlbumArt/" + musicFile.name() + ".png").exists()) {
|
||||
return new Texture(Gdx.files.external("RhythmBullet/AlbumArt/" + musicFile.name() + ".png"));
|
||||
} else {
|
||||
this.albumCover = defaultAlbumCover;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,4 +151,8 @@ public class SongInfo implements Disposable {
|
||||
public boolean hasInformation() {
|
||||
return containsInfo;
|
||||
}
|
||||
|
||||
public FileHandle getMusicFile() {
|
||||
return musicFile;
|
||||
}
|
||||
}
|
57
core/src/zero1hd/rhythmbullet/audio/MusicInfoController.java
Normal file
57
core/src/zero1hd/rhythmbullet/audio/MusicInfoController.java
Normal file
@ -0,0 +1,57 @@
|
||||
package zero1hd.rhythmbullet.audio;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.Preferences;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.badlogic.gdx.utils.Disposable;
|
||||
|
||||
public class MusicInfoController implements Disposable {
|
||||
private MusicList musicList;
|
||||
private ExecutorService exec;
|
||||
private Array<MusicInfo> songInfoArray;
|
||||
private Preferences musicAnnotation;
|
||||
private boolean doneLoading;
|
||||
|
||||
public MusicInfoController(MusicList musicList) {
|
||||
this.musicList = musicList;
|
||||
songInfoArray = new Array<>();
|
||||
exec = Executors.newSingleThreadExecutor();
|
||||
musicAnnotation = Gdx.app.getPreferences("MusicAnnotation");
|
||||
}
|
||||
|
||||
public MusicList getMusicList() {
|
||||
return musicList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-blocking, loads on separate thread.
|
||||
*/
|
||||
public void loadSongInfo() {
|
||||
doneLoading = false;
|
||||
songInfoArray.clear();
|
||||
exec.submit(() -> {
|
||||
for (int i = 0; i < musicList.getAmountOfMusic(); i++) {
|
||||
MusicInfo musicInfo = new MusicInfo(musicList.getMusicList().get(i), musicAnnotation);
|
||||
musicInfo.loadInfo();
|
||||
songInfoArray.add(musicInfo);
|
||||
}
|
||||
doneLoading = true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
exec.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if loading song info is done.
|
||||
* @return
|
||||
*/
|
||||
public synchronized boolean isDoneLoading() {
|
||||
return doneLoading;
|
||||
}
|
||||
}
|
@ -1,70 +1,51 @@
|
||||
package zero1hd.rhythmbullet.graphics.ui.components;
|
||||
|
||||
import com.badlogic.gdx.Preferences;
|
||||
import com.badlogic.gdx.files.FileHandle;
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.badlogic.gdx.graphics.g2d.Batch;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Image;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
|
||||
import com.badlogic.gdx.utils.Disposable;
|
||||
|
||||
import zero1hd.rhythmbullet.audio.SongInfo;
|
||||
import zero1hd.rhythmbullet.audio.MusicInfo;
|
||||
import zero1hd.rhythmbullet.graphics.ui.pages.MusicSelectionPage;
|
||||
|
||||
public class MusicSelectable extends WidgetGroup implements Disposable {
|
||||
private Table table;
|
||||
|
||||
private Image imageIcon;
|
||||
private ShortenedTextLabel displayName;
|
||||
private Label durationLabel;
|
||||
private ShortenedTextLabel authorLabel;
|
||||
|
||||
private FileHandle musicFile;
|
||||
|
||||
private Texture albumCover;
|
||||
private SongInfo songInfo;
|
||||
|
||||
private boolean selected;
|
||||
|
||||
private VerticalGroup vGroup;
|
||||
|
||||
private MusicSelectionPage msp;
|
||||
private MusicInfo musicInfo;
|
||||
|
||||
public MusicSelectable(FileHandle musicFile, Preferences musicAnnotation, Skin skin, Texture defaultAlbumC, MusicSelectionPage msp) {
|
||||
private FileHandle musicFile;
|
||||
|
||||
public MusicSelectable(FileHandle musicFile, Skin skin, MusicSelectionPage msp) {
|
||||
this.musicFile = musicFile;
|
||||
table = new Table(skin);
|
||||
table.setBackground("holo-pane");
|
||||
table.setFillParent(true);
|
||||
vGroup = new VerticalGroup();
|
||||
this.msp = msp;
|
||||
setName(musicFile.name());
|
||||
table.defaults().pad(2f);
|
||||
|
||||
this.albumCover = defaultAlbumC;
|
||||
this.musicFile = musicFile;
|
||||
songInfo = new SongInfo(musicFile, musicAnnotation);
|
||||
|
||||
imageIcon = new Image(albumCover);
|
||||
table.add(imageIcon).size(180f).left().expandX();
|
||||
setName(musicFile.nameWithoutExtension());
|
||||
table.defaults().pad(5f).space(15f);
|
||||
|
||||
displayName = new ShortenedTextLabel(musicFile.nameWithoutExtension().replace('_', ' '), skin, "sub-font", skin.getColor("default"));
|
||||
vGroup.addActor(displayName);
|
||||
table.add(displayName);
|
||||
|
||||
durationLabel = new Label("Loading...", skin, "sub-font", skin.getColor("default"));
|
||||
vGroup.addActor(durationLabel);
|
||||
table.add(durationLabel);
|
||||
|
||||
authorLabel = new ShortenedTextLabel("Loading...", skin, "sub-font", skin.getColor("default"));
|
||||
vGroup.addActor(authorLabel);
|
||||
|
||||
table.add(vGroup).expandX().fillX().center();
|
||||
table.add(authorLabel);
|
||||
|
||||
table.pack();
|
||||
addActor(table);
|
||||
addListener(new ClickListener() {
|
||||
@Override
|
||||
@ -78,27 +59,27 @@ public class MusicSelectable extends WidgetGroup implements Disposable {
|
||||
/**
|
||||
* updates the UI side of information.
|
||||
* needs to be called in thread with gl context.
|
||||
* @param musicInfo the music information for this song.
|
||||
*/
|
||||
public void updateInfo() {
|
||||
displayName.setOriginalText(songInfo.getMusicName());
|
||||
public void updateInfo(MusicInfo musicInfo) {
|
||||
this.musicInfo = musicInfo;
|
||||
displayName.setOriginalText(musicInfo.getMusicName());
|
||||
durationLabel.setText("Runtime: "
|
||||
+ ((songInfo.getDurationInSeconds() / 60 < 1) ? "00" : songInfo.getDurationInSeconds() / 60) + ":"
|
||||
+ ((songInfo.getDurationInSeconds() - (songInfo.getDurationInSeconds() / 60) * 60) < 10
|
||||
? "0" + (songInfo.getDurationInSeconds() - (songInfo.getDurationInSeconds() / 60) * 60)
|
||||
: (songInfo.getDurationInSeconds() - (songInfo.getDurationInSeconds() / 60) * 60)));
|
||||
authorLabel.setOriginalText("Author: " + songInfo.getAuthor());
|
||||
+ ((musicInfo.getDurationInSeconds() / 60 < 1) ? "00" : musicInfo.getDurationInSeconds() / 60) + ":"
|
||||
+ ((musicInfo.getDurationInSeconds() - (musicInfo.getDurationInSeconds() / 60) * 60) < 10
|
||||
? "0" + (musicInfo.getDurationInSeconds() - (musicInfo.getDurationInSeconds() / 60) * 60)
|
||||
: (musicInfo.getDurationInSeconds() - (musicInfo.getDurationInSeconds() / 60) * 60)));
|
||||
authorLabel.setOriginalText("Author: " + musicInfo.getAuthor());
|
||||
authorLabel.setToOriginalText();
|
||||
songInfo.setupTexture(albumCover);
|
||||
albumCover = songInfo.getAlbumCover();
|
||||
imageIcon.setDrawable((new TextureRegionDrawable(new TextureRegion(albumCover))));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
displayName.setTargetWidth((int) (getWidth() - 300));
|
||||
authorLabel.setTargetWidth((int) (getWidth() - 300));
|
||||
displayName.setTargetWidth((int) (getWidth()/3f));
|
||||
authorLabel.setTargetWidth((int) (getWidth()/3f));
|
||||
displayName.resize();
|
||||
authorLabel.resize();
|
||||
super.layout();
|
||||
}
|
||||
|
||||
@ -112,21 +93,8 @@ public class MusicSelectable extends WidgetGroup implements Disposable {
|
||||
super.act(delta);
|
||||
}
|
||||
|
||||
public FileHandle getMusicFile() {
|
||||
return musicFile;
|
||||
}
|
||||
|
||||
public SongInfo getAudioInfo() {
|
||||
return songInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
songInfo.dispose();
|
||||
}
|
||||
|
||||
public boolean isMusicInvalid() {
|
||||
return songInfo.isInvalidMusic();
|
||||
return musicInfo.isInvalidMusic();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -152,4 +120,16 @@ public class MusicSelectable extends WidgetGroup implements Disposable {
|
||||
public float getPrefHeight() {
|
||||
return table.getMinHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
}
|
||||
|
||||
public FileHandle getMusicFile() {
|
||||
return musicFile;
|
||||
}
|
||||
|
||||
public MusicInfo getMusicInfo() {
|
||||
return musicInfo;
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,6 @@ package zero1hd.rhythmbullet.graphics.ui.pages;
|
||||
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.Input.Keys;
|
||||
@ -28,7 +26,8 @@ import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
|
||||
import zero1hd.rhythmbullet.audio.MusicManager;
|
||||
import zero1hd.rhythmbullet.audio.SongInfo;
|
||||
import zero1hd.rhythmbullet.audio.MusicInfo;
|
||||
import zero1hd.rhythmbullet.audio.MusicInfoController;
|
||||
import zero1hd.rhythmbullet.audio.MusicListController;
|
||||
import zero1hd.rhythmbullet.graphics.ui.components.MusicSelectable;
|
||||
import zero1hd.rhythmbullet.graphics.ui.components.ScrollText;
|
||||
@ -38,6 +37,7 @@ public class MusicSelectionPage extends Page implements Observer {
|
||||
private boolean extraInfoDone;
|
||||
|
||||
private MusicListController mc;
|
||||
private MusicInfoController mic;
|
||||
private Array<MusicSelectable> selectables;
|
||||
private Table musicTable;
|
||||
private ScrollPane musicTableScrollPane;
|
||||
@ -61,13 +61,15 @@ public class MusicSelectionPage extends Page implements Observer {
|
||||
private int musicSelectableIndex;
|
||||
|
||||
private TextButton beginButton;
|
||||
|
||||
private int uiSongCount;
|
||||
private float scrollTimer, scrollDelay = 0.2f, scrollDelMod, songSelectionTimer;
|
||||
private ExecutorService exec;
|
||||
|
||||
public MusicSelectionPage(Skin skin, MusicListController musicListController, AssetManager assetManager, Vector3 cameraTarget, AnalysisPage ap) {
|
||||
setTextureBackground(assetManager.get("gradients.atlas", TextureAtlas.class).findRegion("red-round"));
|
||||
this.skin = skin;
|
||||
this.mc = musicListController;
|
||||
mic = new MusicInfoController(mc.getMusicList());
|
||||
musicFileAnnotation = Gdx.app.getPreferences("music_file_annotation");
|
||||
this.assets = assetManager;
|
||||
musicTable = new Table();
|
||||
@ -137,8 +139,6 @@ public class MusicSelectionPage extends Page implements Observer {
|
||||
ap.processSong(mc.getMusicList().getAudioData(getSelectedMusic()));
|
||||
}
|
||||
});
|
||||
|
||||
exec = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -175,14 +175,20 @@ public class MusicSelectionPage extends Page implements Observer {
|
||||
}
|
||||
}
|
||||
super.act(delta);
|
||||
|
||||
if (uiSongCount < selectables.size) {
|
||||
musicTable.add(selectables.get(uiSongCount)).expandX().fillX();
|
||||
uiSongCount++;
|
||||
musicTable.row();
|
||||
}
|
||||
}
|
||||
|
||||
public FileHandle getSelectedMusic() {
|
||||
return currentlySelected.getMusicFile();
|
||||
}
|
||||
|
||||
public SongInfo getSelectedMusicInfo() {
|
||||
return currentlySelected.getAudioInfo();
|
||||
public MusicInfo getSelectedMusicInfo() {
|
||||
return currentlySelected.getMusicInfo();
|
||||
}
|
||||
|
||||
public void refreshUIList() {
|
||||
@ -191,6 +197,8 @@ public class MusicSelectionPage extends Page implements Observer {
|
||||
musicInfoTable.clear();
|
||||
musicSubInfo.clear();
|
||||
extraInfoDone = false;
|
||||
uiSongCount = 0;
|
||||
mic.loadSongInfo();
|
||||
|
||||
for (int i = 0; i < selectables.size; i++) {
|
||||
selectables.get(i).dispose();
|
||||
@ -199,44 +207,24 @@ public class MusicSelectionPage extends Page implements Observer {
|
||||
Gdx.app.debug("MusicSelectionPage", "Refreshing...");
|
||||
for (int i = 0; i < mc.getMusicList().getAmountOfMusic(); i++) {
|
||||
|
||||
MusicSelectable selectable = new MusicSelectable(mc.getMusicList().getSongFileHandleFromIndex(i), musicFileAnnotation, skin, assets.get("defaultCover.png", Texture.class), this);
|
||||
MusicSelectable selectable = new MusicSelectable(mc.getMusicList().getSongFileHandleFromIndex(i), skin, this);
|
||||
selectables.add(selectable);
|
||||
|
||||
musicTable.add(selectable).expandX().fillX();
|
||||
musicTable.row();
|
||||
}
|
||||
|
||||
exec.submit(() -> {
|
||||
for (int i = 0; i < selectables.size; i++) {
|
||||
MusicSelectable selectable = selectables.get(i);
|
||||
selectable.getAudioInfo().loadInfo();
|
||||
Gdx.app.postRunnable(() -> {
|
||||
selectable.updateInfo();
|
||||
});
|
||||
}
|
||||
extraInfoDone = true;
|
||||
|
||||
musicInfoTable.add(songTitle).width(musicInfoTable.getWidth()*0.6f).spaceBottom(30f);
|
||||
musicInfoTable.row();
|
||||
musicSubInfo.add(author);
|
||||
musicSubInfo.row();
|
||||
musicSubInfo.add(songLength);
|
||||
musicSubInfo.row();
|
||||
musicSubInfo.add(previousTop);
|
||||
musicSubInfo.row();
|
||||
musicSubInfo.add(ratedDifficulty);
|
||||
musicSubInfo.pack();
|
||||
musicInfoTable.add(musicSubInfo).spaceBottom(20f);
|
||||
musicInfoTable.row();
|
||||
musicInfoTable.add(albumCover).size(musicInfoTable.getWidth()/2f);
|
||||
musicInfoTable.row();
|
||||
musicInfoTable.add(beginButton).spaceTop(20f).fillX();
|
||||
|
||||
if (currentlySelected != null) {
|
||||
updateInformation();
|
||||
}
|
||||
Gdx.app.debug("MusicSelectionPage", "Refresh complete. " + selectables.size + " songs loaded.");
|
||||
});
|
||||
musicInfoTable.add(songTitle).width(musicInfoTable.getWidth()*0.6f).spaceBottom(30f);
|
||||
musicInfoTable.row();
|
||||
musicSubInfo.add(author);
|
||||
musicSubInfo.row();
|
||||
musicSubInfo.add(songLength);
|
||||
musicSubInfo.row();
|
||||
musicSubInfo.add(previousTop);
|
||||
musicSubInfo.row();
|
||||
musicSubInfo.add(ratedDifficulty);
|
||||
musicSubInfo.pack();
|
||||
musicInfoTable.add(musicSubInfo).spaceBottom(20f);
|
||||
musicInfoTable.row();
|
||||
musicInfoTable.add(albumCover).size(musicInfoTable.getWidth()/2f);
|
||||
musicInfoTable.row();
|
||||
musicInfoTable.add(beginButton).spaceTop(20f).fillX();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -276,7 +264,6 @@ public class MusicSelectionPage extends Page implements Observer {
|
||||
if ((musicSelectableIndex = (musicTable.getChildren().indexOf(currentlySelected, true) - 1)) < 0) {
|
||||
musicSelectableIndex = musicTable.getChildren().size-1;
|
||||
}
|
||||
deselectAll();
|
||||
((MusicSelectable)musicTable.getChildren().get(musicSelectableIndex)).select();
|
||||
musicTableScrollPane.scrollTo(currentlySelected.getX(), currentlySelected.getY(), currentlySelected.getWidth(), currentlySelected.getHeight());
|
||||
}
|
||||
@ -312,20 +299,23 @@ public class MusicSelectionPage extends Page implements Observer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be called when everything is loaded.
|
||||
*/
|
||||
private void updateInformation() {
|
||||
songTitle.setText(currentlySelected.getAudioInfo().getMusicName(), null);
|
||||
author.setText("Author: " + currentlySelected.getAudioInfo().getAuthor());
|
||||
songTitle.setText(currentlySelected.getMusicInfo().getMusicName(), null);
|
||||
author.setText("Author: " + currentlySelected.getMusicInfo().getAuthor());
|
||||
|
||||
long lengthInSeconds = currentlySelected.getAudioInfo().getDurationInSeconds();
|
||||
long lengthInSeconds = currentlySelected.getMusicInfo().getDurationInSeconds();
|
||||
int min = (int) (lengthInSeconds/60);
|
||||
int sec = (int) (lengthInSeconds - (min*60));
|
||||
songLength.setText("Length: " + min + ":" + (sec > 9 ? sec : "0" + sec));
|
||||
|
||||
previousTop.setText("Highscore: " + currentlySelected.getAudioInfo().getPreviousTop());
|
||||
previousTop.setText("Highscore: " + currentlySelected.getMusicInfo().getPreviousTop());
|
||||
|
||||
String difficulty = (getSelectedMusicInfo().getRatedDifficulty() == -1 ? "N/A" : String.valueOf(getSelectedMusicInfo().getRatedDifficulty()));
|
||||
ratedDifficulty.setText("Rated Difficulty: " + difficulty);
|
||||
|
||||
albumCover.setDrawable((new TextureRegionDrawable(new TextureRegion(currentlySelected.getAudioInfo().getAlbumCover()))));
|
||||
albumCover.setDrawable((new TextureRegionDrawable(new TextureRegion(currentlySelected.getMusicInfo().getAlbumCover()))));
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ public class MainMenu extends ScreenAdapter implements TransitionAdapter {
|
||||
private RhythmBullet core;
|
||||
|
||||
private MusicListController mlc;
|
||||
|
||||
private float lerpAlpha;
|
||||
|
||||
private ShaderProgram gaussianBlurShader;
|
||||
|
Loading…
x
Reference in New Issue
Block a user