I looked all over the net in order to find out if its possible to change the renderer of a GLSurfaceView on the flight. The reason is that I want to change the OpenGl program, and initiate all the attributes and unified params from its vertex and fragment shader and I don't want the any change would require to create a brand new GLSurfaceView with a brand new Renderer.
It seems like reasonable operation that should be doable.
Note: I haven't implemented the following.
GLSurfaceView.Renderer is an interface. Implement it three times. Twice for your different OpenGL renderers, and one time attached to the GLSurfaceView. The latter only dispatches to one of the former, and allows to change the renderer to which it dispatches. The code must hold a reference to this renderer, and eventually must be synchronized to the draw calls (though I don't know).
Be aware that you cannot easily switch OpenGLES context data. It is shared between all renderer instances.
class DispatchingRenderer implements GLSurfaceView.Renderer {
private class Renderer1 implements GLSurfaceView.Renderer {
...
}
private class Renderer2 implements GLSurfaceView.Renderer {
...
}
public DispatchingRenderer() {
this.r1 = new Renderer1();
this.r2 = new Renderer2();
this.currentRenderer = this.r1;
}
public void ToggleRenderer() {
if(this.currentRenderer == this.r1) {
this.currentRenderer = this.r2;
} else if (this.currentRenderer == this.r2) {
this.currentRenderer = this.r1;
}
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// do one-time setup
}
public void onSurfaceChanged(GL10 gl, int w, int h) {
this.currentRenderer.onSurfaceChanged(gl, w, h);
}
public void onDrawFrame(GL10 gl) {
this.currentRenderer.onDrawFrame(gl);
}
}
Related
I'm having trouble to change my rendered mesh in my GLSurfaceView, and more precisely in the Renderer, I guess there is something I didn't understood in the workflow of the Renderer class.
So, let's consider we have this :
public class MyRenderer extends GLSurfaceView.Renderer{
private Mesh aMesh;
#Override
public void onSurfaceCreated(GL10 gl, EGLConfig config){
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
aMesh = new Mesh("TestMesh1");
}
#Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
...Creating viewport...
}
#Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(gl.GL_COLOR_BUFFER_BIT);
...all Maths to create mvpMatrix...
aMesh.draw(mvpMatrix);
}
}
So this is perfectly working, aMesh is display on the screen with all the good settings. Now, I want to change this mesh when the user is pressing a button, and to refresh my renderer I created a specific method in the renderer, as follow :
public class MyRenderer extends GLSurfaceView.Renderer{
private Mesh aMesh;
public void changeMesh(String newMeshName){
GLES20.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);
....
aMesh=new Mesh(newMeshName);
}
#Override
public void onSurfaceCreated(GL10 gl, EGLConfig config){
....
}
#Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
...
}
#Override
public void onDrawFrame(GL10 gl) {
aMesh.draw(mvpMatrix);
}
}
So in my mind, calling changeMesh would make onDrawFrame method to redraw aMesh with the new model, right?
My problem is that the result is an empty renderer. It's not even crashing or so. When I call changeMesh(), the previously well displayed mesh disappear (as expected), but the new one is not draw.
So I'm wondering, does it requires to create a new Renderer(which seems a little bit heavy)? Is there a way to ask a pass in OnSurfaceCreated manually? I'm a bit lost on this bug because it's kinda unexpected.
Part of the problem was the call to new, I couldn't explain exactly the reasons, but it seems it's somehow breaking links between OpenGL Objects and my buffers. I solved the problem with a kind of "setter" which just change all datas in an instance of Mesh to suit the datas of an other Mesh, this way I could save the memory address of the first instance.
It's a bit strange but well this solved my problem, maybe it can help someone else.
I have a standard GLSurfaceView class:
public class TestSurfaceView extends GLSurfaceView {
public MainRenderer mRenderer;
public GStreamerSurfaceView(Context context) {
super(context);
setEGLContextClientVersion(2);
mRenderer = new MainRenderer(context);
setRenderer(mRenderer);
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
}
I have a Renderer class that implements GLSurfaceView.Renderer:
public class MainRenderer implements GLSurfaceView.Renderer {
private int[] hTex;
private SurfaceTexture mSTexture;
private Context context;
MainRenderer(Context c) {
context = c;
}
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
}
public void onDrawFrame(GL10 unused) {
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
}
public void onSurfaceCreated(GL10 arg0, javax.microedition.khronos.egl.EGLConfig arg1) {
}
}
In a separate JNI thread, I have some video data (YUV format) uploaded to an OpenGLES texture. I receive in Java the notification that a new texture is available and I have the corresponding texture id.
How can I display the content of this texture in the Renderer class with minimum performance impact?
For cases where the frames are coming from Camera or MediaCodec there are some very efficient solutions. It sounds like you're generating or decoding the video in software, though, which puts a mild spin on things (though you do have a SurfaceTexture declared in your code, which is odd). The trick is the "OpenGL ES texture" part, because the texture is associated with the EGL context, and the EGL context can only be active in one thread at a time.
Because you're using GLSurfaceView, rather than plain SurfaceView, you don't have control over the EGL context in the GLSurfaceView's renderer thread. The easiest way to work around this is to jump through some hoops to create a second EGL context that is shared with the first. Once you've done that, the texture created in the separate JNI thread will be available to the GLSurfaceView renderer thread.
You can find an example of this in Grafika's "show + capture camera" activity. If you look at the onDrawFrame() method in CameraCaptureActivity.java you can see it calling updateSharedContext(), which passes a message to the thread running TextureMovieEncoder to cause it to run handleUpdateSharedContext(), which (re-)creates the surface used to feed a video encoder.
If you use plain SurfaceView instead, and do your own EGL and thread management, the code will be less twisty. You can create both contexts at once, then just pass one to the thread that's producing images. You could also create a single context and use eglMakeCurrent() to shift it between threads, but that could be expensive on some platforms.
Update: the Grafika "show + capture camera" implementation has a race condition; see this bug report for details on the problem and solution. You have to perform some extra steps when creating a texture in one thread and using it in another. It's usually better to do everything in one context on one thread. The other activities in Grafika use a plain SurfaceView and do their own EGL context and thread management.
I'm developing an engineering app for android. The thing is that I need to draw: rectangles, figures made of rectangles, and their dimensions. Then if you touch one extreme of one dimension you are able to make that dimension of the rectangle longer or shorter.
I am implementing the next scheme in order to achieve my goal:
class DrawFigureWithDimensions extends View{
// implementation of the draw methods
// ...
class Rectangles{
// characterization of the attributes needed for each "rectangle"
// ...
class DimensionPositionType{ ... }
class DimensionExtremeType{ ... }
}
class DrawRectangleWithDimension extends DrawFiguresWithDimensions{ ... }
class DrawBoxWithDimension extends DrawFiguresWithDimensions{ ... }
...
Then, I have a problems implementing the inner classes of DimensionPositionType and DimensionExtremeType, I can't find out how to declare them in a suitable way. I need to be able to decide in the extended classes, like DrawRectangleWithDimensions, for example, what type of extreme of the dimension I need: fixed or movable. Something like this:
public class DrawRectangleWithDimensions extends DrawFiguresWithDimensions {
public DrawRectangleWithDimensions(Context context) {
super(context);
}
public void setFigure(double width, double height) {
figureRectangles = new Rectangle[1];
figureRectangles[0] = new Rectangle(0, 0, width, height);
figureRectangles[0].setHorizontalDimensionLeftExtremeType(FIXED);
figureRectangles[0].setHorizontalDimensionRightExtremeType(MOVABLE);
}
For instance, this is the code that I have for the inner class DimensionExtremeType:
class DimensionExtremeType{
boolean FIXED;
boolean MOVABLE;
DimensionExtremeType(String arg){
if(arg == "FIXED"){
setFixedExtreme();
}else if(arg == "MOVABLE"){
setMovableExtreme();
}
}
public void setFixedExtreme(){
FIXED = true;
MOVABLE = false;
}
public void setMovableExtreme(){
FIXED = false;
MOVABLE = true;
}
public String getDimensionExtremeType(){
if(FIXED==true){
return "FIXED";
}else if(MOVABLE==true){
return "MOVABLE";
}else {
return null;
}
}
}
I just know about the existence of the class enum, which solves the problem of design that I had. It's a much easier way of carrying it out. According to the example that I wrote in the wording of the question, this is the code using the class enum:
static class Rectangle{
// ...
DimensionExtremeType horizontalDimensionLeftExtremeType;
DimensionExtremeType horizontalDimensionRightExtremeType;
DimensionExtremeType verticalDimensionUpperExtremeType;
DimensionExtremeType verticalDimensionLowerExtremeType;
// =-=-=-= DIMENSION POSITION TYPES =-=-=-=
public enum DimensionExtremeType{FIXED, MOVABLE}
}
Then, in the extended classes, for instance DrawRectanglesWithDimensions:
public class DrawRectangleWithDimensions extends DrawFiguresWithDimensions {
public DrawRectangleWithDimensions(Context context) {
super(context);
}
public void setFigure(float width, float height) {
figureRectangles = new Rectangle[1];
figureRectangles[0] = new Rectangle(0, 0, width, height);
figureRectangles[0].horizontalDimensionLeftExtremeType = Rectangle.DimensionExtremeType.FIXED;
figureRectangles[0].horizontalDimensionRightExtremeType = Rectangle.DimensionExtremeType.MOVABLE;
figureRectangles[0].verticalDimensionUpperExtremeType = Rectangle.DimensionExtremeType.MOVABLE;
figureRectangles[0].horizontalDimensionRightExtremeType = Rectangle.DimensionExtremeType.MOVABLE;
}
}
The Setup : A RelativeLayout with a GLSurfaceView and a Button as shown in the image..
The Problem: Lets say I have other triangle models (The one in the picture being the initial model)... I wish to change the models cyclically on click of the button. Since button is on the UI thread and glSurfaceView runs on a separate thread, I don't exactly know how to pass the info/instruction to it. I know there is this thing called Handler in Android which might be useful in this case... But I need some help here..
Edit: If Handler is the right way, I need to know how to add Looper to that Handler... The documentation says add looper.prepare() at the start of run() method.. But the glSurfaceView creates thread implicitly, resulting in no run() method directly available..
I don't think it is necessary to use handlers to solve this issue but you may need to adjust the way you organise your classes.
Here is an example of an organisational structure that might solve your issue:
Activity Class
public class MainActivity extends Activity {
private int modelNumber = 0;
private ArrayList<Model> models = new ArrayList<Model>();
private YourRendererClass renderer;
#Override
public void onCreate(Bundle savedInstanceState) {
...
// Setup GLSurfaceView
GLSurfaceView surface = new GLSurfaceView(this);
setContentView(surface);
renderer = new YourRendererClass();
surface.setRenderer(renderer);
// Set up models
models.add(new Model(x, y, size etc..));
models.add(new Model(x, y, size etc..));
models.add(new Model(x, y, size etc..));
etc.
// Display first model
renderer.setCurrentModel(models.get(modelNumber));
...
}
// Called by the button press:
// Use android:onClick="onClick"
// in your layout xml file within button
public void onClick(View view){
// Make it loop round
modelNumber++;
if(modelNumber>=models.size()){
modelNumber=0;
}
// Display current model
renderer.setCurrentModel(models.get(modelNumber));
}
}
Renderer Class
public class YourRendererClass implements Renderer {
private Model currentModel;
#Override
public void onDrawFrame(GL10 gl) {
// ** Your existing set-up code **//
// Draw model
if (currentModel!=null){
currentModel.draw(gl);
}
}
public void setCurrentModel(Model model){
currentModel = model;
}
}
Model class
public class Model {
// Holds model information
private int size;
private int x;
private int y;
// etc...
public model(int x, int y, int size etc...){
this.x=x;
this.y=y;
this.size=size;
// etc....
}
public void draw(GL10 gl) {
// ** Draw model based on model information fields above **
}
}
The above code is untested as I don't have access to your drawing code but the structure should work if implemented correctly. I've tried to make it clear where you'll have to insert your own code to make it work. In particular I wasn't sure what defines each of your different models so you'll need to include sufficient local variables within the Model class to define them.
I hope my answer helps, let me know if you have any questions.
Tim
You should look at queueEvent! It's a very convenient way to pass informations from UI Thread to renderer Thread:
queueEvent(new Runnable(){
#Override
public void run() {
mRenderer.method();
}});
EDIT: I'll try and make it clearer sorry for the confusion.
I have two livewallpapers one is called the Past one is called the Future, what I want to do is put both into one livewallpaper (sort of a two for one deal) but let the user decide which one they want to load.
How I had it set up for one (let's say the Past) I had the onDraw method running in a class called the Past (it did not impliment anything) just past the onDraw and put the whole livewallpaper togeather.
In the livewallpaper engine I had this.
public class ThePastActivity extends WallpaperService {
#Override
public Engine onCreateEngine() {
return new ThePastActivityEngine();
}
class ThePastActivityEngine extends Engine {
private Past _past;
public ThePastActivityEngine() {
this._past = new Past();
this._past.initialize(getBaseContext(), getSurfaceHolder());
}
#Override
public void onVisibilityChanged(boolean visible) {
if(visible){
this._past.render();
}
}
#Override
public void onSurfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
super.onSurfaceChanged(holder, format, width, height);
}
#Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
this._past.start();
}
#Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
this._past.stop();
}
#Override
public void onOffsetsChanged(float xOffset, float yOffset,float xStep, float yStep, int xPixels, int yPixels) {
this._past.drawXOff = Math.round((this._blimp.theBackgroundImage.getWidth() - initFrameParamsWidth()) * -xOffset);
this._past.drawYOff = Math.round((this._blimp.theBackgroundImage.getHeight() - initFrameParams()) * -yOffset);
this._past.render();
}
}
Now I have two instead of one. The new one is called Future so now I have it like so:
public class ThePastActivity extends WallpaperService {
public static final String SHARED_PREFS_NAME = "livewallpapersettings";
public static final String PREFERENCE_BACK = "livewallpaper_back";
#Override
public Engine onCreateEngine() {
return new ThePastActivityEngine();
}
class ThePastActivityEngine extends Engine implements SharedPreferences.OnSharedPreferenceChangeListener{
private SharedPreferences prefs;
private String whichEra;
private Past _past;
private Future _future;
//make a new name ChooseEra and let it become either Past or Future
private ChooseEra _chooseEra;
public ThePastActivityEngine() {
this._chooseEra = new ChooseEra();
this._past.initialize(getBaseContext(), getSurfaceHolder());
prefs = TheSteampunkCityActivity.this.getSharedPreferences(SHARED_PREFS_NAME, 0);
prefs.registerOnSharedPreferenceChangeListener(this);
onSharedPreferenceChanged(prefs, null);
}
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
whichEra=(prefs.getString(PREFERENCE_BACK, "past"));
// from here I want to get either the "past" or "future"
// make the ChooseEra to be either Past or Future
// and use that for this livewallpaper engine instead
}
#Override
public void onVisibilityChanged(boolean visible) {
if(visible){
this._chooseEra.render();
}
}
#Override
public void onSurfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
super.onSurfaceChanged(holder, format, width, height);
}
#Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
this._past.start();
}
#Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
this._chooseEra.stop();
}
#Override
public void onOffsetsChanged(float xOffset, float yOffset,float xStep, float yStep, int xPixels, int yPixels) {
this._chooseEra.drawXOff = Math.round((this._chooseEra.theBackgroundImage.getWidth() - initFrameParamsWidth()) * -xOffset);
this._chooseEra.drawYOff = Math.round((this._chooseEra.theBackgroundImage.getHeight() - initFrameParams()) * -yOffset);
this._chooseEra.render();
}
}
So chooseEra has to become either Future or Past so it reads one of the two classes only and passes the arguments along through the engine.
The issue I am having is making ChooseEra to become either Past or Future. Normally using a method is easy to do but this is the first time I am trying to make it change the class name so when I put
private ChooseEra _chooseEra;
it makes no sense at all, I tried ChooseEra = Past and ChooseEra = Future in an if else statement comparing the prefs of "past" and "future" but no luck.
Again any help is greatly appreciated.
Sam
Your having typing issues. If you want to return the implementation to use (either Future or Past) then they need a shared interface or base class that you can use. I'll pick the name to be Era. Define all of your variables of that shared type and it will work. For example:
public Era readTheEra(SharedPreferences prefs) {
String whichEra = prefs.getString(PREFERENCE_BACK, "past");
Era era = whichEra.equals("past") ? new Past() : new Future();
return era;
}
Notice Era is marked as the return type of the method (you had void which won't work). Also notice I simply passed the SharedPreferences to the method, and encapsulated the code to extract the preference value in the method. That way you aren't writing to instance variables (that you don't need), then reading in other methods. Just pass the information to the method, and don't save the intermediate steps. The only thing you need is the Era reference to use. The value of the preference isn't needed after you instantiate the correct class.
You'll need to mark the two concrete implementations as implementing the Era Interface:
public interface Era {
// put methods here you need both implementations to
// have so you can work from Era interface and not the
// individual concrete clases.
}
public class Past implements Era {
...
}
public class Future implements Era {
...
}
public class Engine {
private Era era;
...
private Era readTheEra(SharedPreferences prefs) {
String whichEra = prefs.getString(PREFERENCE_BACK, "past");
Era era = whichEra.equals("past") ? new Past() : new Future();
return era;
}
}
I picked to use Interfaces because your question isn't clear enough to know if you need classes or can use simply Interfaces. But all of the same thing applies if you need to use some class like Activity or whatever. Subclass Activty with an abstract base class, and Past and Future should subclass the abstract base class.