Rendering von Bitmaps

D

Duckemai

Fortgeschrittenes Mitglied
6
Hallo zusammen,

ich versuche derzeit ein kleines Spiel zu programmieren. Dafür habe ich bitmaps auf dem Screen, die ich hin und her drehe und über den Bildschirm laufen lasse. Da die Images der Bitmaps gerade Ränder haben, sehe ich nun, das die Ränder der Bitmapzeichnung beim Verschieben über das Hintergrundbild immer wie abgebrochen wirken. Also eine lange Linie nach dem Drehen in vielleicht 3 schräg laufende Linien unterteilt wird, mit kantigen Unterbrechungen entsprechend der Pixel. Kann man die Bitmaps in Android auch rendern, so dass weiche Ränder entstehen. Kennt jemand das passende Stichwort dazu oder besser noch ein Tutorial?

Vielen Dank
Duckemai
 
Zuletzt bearbeitet:
Das Fachwort dazu ist Antialiasing. Google mal danach, vielleicht hilft. Ich selber habe keine Erfahrung dazu mit einer Bitmap.
 
  • Danke
Reaktionen: Duckemai
Ja perfekt! Genau das war das Stichwort. Das Rendering klappt nun super,
leider ruckeln die Bitmaps nun. Habe jetzt schon viel über invalidate() gelesen, aber wirklich funktionieren tut es nicht.

Da heißt es, wer einen Thread benutzt muss invalidate() nicht aufrufen. Ich
benutze einen, Bitmaps ruckeln aber. Auch habe ich sowohl den Aufruf in einer onDraw() und einer doDraw() versucht. (Ich benutze eine SurfaceView). Invalidate() bringt das System zum Absturz, bei postInvalidate() ruckeln die Bitmaps munter weiter.

Kann mir jemand sagen, wo das invalidate/postinvalidate denn nun hingehört, damit es funktioniert? Am besten bei Benutzung der doDraw().

Vielen Dank,
Duckemai
 
Ohne detaillierte Codekenntnis ist das kaum zu beantworten.

Aber allgemein fährt man mit Off-Screen-Rendering ruckelfreier.
D.h. du malst in eine Bitmap hinein (die erstmal nicht auf dem Bildschirm
landet) und diese Bitmap kannst Du dann in deinem SurfaceView (zB in einem
extra Thread) mit einer Framerate deiner Wahl en bloc schreiben.
 
  • Danke
Reaktionen: Duckemai
Das ist ein sehr interessantes Stichwort. Habe gleich mal Google angeworfen um vielleicht so etwas wie ein Tutorial zu finden. Leider scheint Off-Screen-Rendering vorwiegend für OpenGL sehr populär zu sein. Ich arbeite aber mit Java2D. Kennst Du vielleicht ein Tutorial dazu?

Vielen Dank
Duckemai
 
Das eigentlich zu simpel für ein Tutorial.

Also im Prizip geht das so (aus der Hüfte geschossen):

1.) ein eigener Canvas:
Code:
package my.package;

class OffScreenView extends View {
   public OffScreenView(Context context, AttributeSet attrs) {
     super(context, attrs);
   }

   Bitmap buffer = null;
   Canvas offScreen = null;

   public void onDraw( Canvas c ) {
     if( buffer==null ) {
      buffer = Bitmap.createBitmap( c.getWidth(), c.getHeight(), Bitmap.Config.ARGB_8888 );
      offScreen = new Canvas(buffer);
     }
     c.drawBitmap(buffer,0,0,null);
   }

}

Nun malst du mit offScreen anstatt mit c. Und machst kein postInvalidate oder so!

2.) Im Lauyout definieren:
Code:
<my.package.OffScreenView
    android:layout_width="..."
    ...
   android:id="@+id/offscreen"
/>
3.) Update thread bauen:

Code:
class Updater implements Runnable {
  OffScreenView theView;
  boolean running;
  public Updater( OffScreenView theView ) { this.theView = theView; ... }
  public void run() {
     while( running ) {
        theView.postInvalidate();
        try { Thread.sleep( 1000 / FRAMERATE ); } catch( InterruptedException wont_care ) {}
     }
  }
}
4.) In der Activity den Updater einbinden:

Code:
....MyActivity...

   private Updater upd;

   public void onCreate( Bundle bundle ) {
     super.onCreate(nundle);
     upd = new Updater( (OffScreenView)findViewById(R.id.offscreen)) );
     ...
  }

   public void onResume() {
      super.onResume();
      new Thread(upd).start();
   }
      
   public void onPause() {
      super.onPause();
      upd.running = false;
   }
 
Zuletzt bearbeitet:
  • Danke
Reaktionen: Duckemai
Wow!!! Vielen Dank für die Mühe. Ich versuche es gleich mal und gebe Feedback.
 
Tja, was soll ich sagen. Ich habe erstmal deine Ausarbeitungen 1 zu 1 übernommen. Ich war ganz überrascht, dass das Spiel mit Deinem Vorschlag ohne zu murren startete. (Respekt! Wer so aus der Hüfte schießen kann, der braucht auch nicht mehr zu zielen! :thumbup:)

Ich habe es durchdebugged.
Die Anwendung läuft durch den start() im Activity,
dann durch Bitmap.createBitmap in der OffScreenView und durch die drawBitmap der OffScreenView. Aber alles nur einmal und dann nie wieder. :crying:

Also habe ich boolean running im Updater auf true initialisiert. Jetzt sieht
es schon anders aus:
Die Anwendung läuft durch den start() im Activity,
jetzt endlich auch durch theView.postInvalidate() im Updater
dann durch Bitmap.createBitmapin der OffScreenView
und ab hier läuft er im Wechsel zwischen postInvalidate() und drawBitmap.

Sah aus, als würde es klappen. Doch die Bitmaps ruckelten mehr als vorher. :ohmy:


Der Durchlaufen von Hauptthread(HT), postInvalidate(pI) und Buffer_onDraw(B_oD) sieht in etwa so aus:
1 x HT
1-3 x pI
1 x B_oD
...und wieder von vorne.

Ich habe dann mit dem Thread.sleep herumgespielt. Mal hoch gesetzt, mal
runter. Mal ausgeschaltet. Irgendwie scheint mein Hauptthread, dadurch dass ich ihn auf FPS begrenze, das Ruckeln mitzuverursachen. Denn dort gibt es ebenfalls einen Thread.sleep. Womit ich wieder bei dem leidigen Thema einer GameLoop bin. Da habe ich noch nichts gefunden, das ein schnelles System ruckelfrei ausbremsen kann.

Gruß
Duckemai
 
Zuletzt bearbeitet:
Duckemai schrieb:
Der Durchlaufen von Hauptthread(HT), postInvalidate(pI) und Buffer_onDraw(B_oD) sieht in etwa so aus:
1 x HT
1-3 x pI
1 x B_oD
...und wieder von vorne. ?!

Was soll das sein?
Ein Stacktrace oder die Threadliste im Debugger?
Was ist "Hauptthread"?
Kann es sein, dass du mehrere Updater am laufen hast?

Ich weiss auch nicht, wo du in den offscreen reinmalst. Das sollte
nicht aus dem Code aus meinem Beispiel sein, sondern
in einem Extrathread (das wäre dann im Prinzip die Gameloop. Achtung:
auch davon darf nur eine Laufen). Übrigens ein Thread.yield() in der Gameloop kann sinnvoll sein.

Man kann Updater und Gameloop auch zusammenlegen. Das macht man typischerweise so:

Code:
public void run() {
  while( running ) {
     long start = System.currentTimeMillis();
     .. neues bild aufbauen .. 
     long time = System.currentTimeMillis() - start;
     .. 1000 / FRAMERATE - time warten ..
    theView.postInvalidate();
  }
Vielleicht ruckelt es aber auch nur deshalb, weil Du schlicht zu viel darstellen willst. :D

ADD: Guck dir mal die Klasse SurfaceView an, die ist für asyncrones Malen von Google vorgesehen -
habe selbst aber auch noch nicht verstanden wie genau, und ob das OffScreen rendering überflüssig macht.
 
Zuletzt bearbeitet:
  • Danke
Reaktionen: Duckemai
DieGoldeneMitte schrieb:
ADD: Guck dir mal die Klasse SurfaceView an, die ist für asyncrones Malen von Google vorgesehen -
habe selbst aber auch noch nicht verstanden wie genau, und ob das OffScreen rendering überflüssig macht.

Es wird einfach intern zwischen zwei Surfaces gewechselt (Surfaceflinger?). Einer wird gerade offscreen 'bemalt' und einer wird angezeigt. Double Buffering heisst das sonst überall. Dadurch spart man das eine Anzeigen der Offscreen-Bitmap und es dürfte ähnlich schnell sein wie vorher.
 
  • Danke
Reaktionen: Duckemai
@miha:
Heißt das, wenn ich die Klasse SurfaceView benutze (statt z.B. nur die View), dass dann schon automatisch das DoubleBuffering enthalten ist?

@dieGoldeneMitte:
Das ist nur eine Auflistung von mir. Hauptthread ist der Thread der das Spiel, also onDraw(), updatePhysics() usw sowie die GameLoop (also FPS usw) steuert. Dieser Hauptthread wird im
Wechsel mit Deinem Thread durchlaufen. Und nein, ich habe keinen anderen Updater.
 
Zuletzt bearbeitet:
Ja. LunarLander ist da ein gutes Beispiel.
 
  • Danke
Reaktionen: Duckemai und DieGoldeneMitte
Ah, ok. LunarLander kenne ich. Wusste nur nicht, dass damit auch schon das DoubleBuffering automatisch mit abgearbeitet wird. Dann liegt es wohl doch an meinem Thread.sleep()...

Vielen Dank!
 
Wichtig zu beachten dabei ist dass die SurfaceView nach der Darstellung der Grafik die Bitmap immer komplett löscht. Du kannst damit also kein inkrementelles Rendering machen, sondern musst Deine Grafik immer vollständig neu berechnen.

Um das zu umgehen musst Du Dich um's Double-Buffering dann doch wieder selbst kümmern, d.h. Grafik in eine eigene Bitmap rendern und diese dann in die SurfaceView en Block reinkopieren.

Generell hilft Dir DoubleBuffering nur beim Flackern. Beim Ruckeln hast Du ein anderes Problem. Ohne Deinen Code zu kennen würde ich auf Gargabe Collection tippen. Schau mal im log ob während Dein Spiel läuft eine Garbage Collection angestossen wird.
 
  • Danke
Reaktionen: Duckemai
Ein sicherlich lohnenswerter Hinweis von Dir! Habe mal während das Spiel lief den Debugger laufen lassen und mir in der LogCat angeschaut, was da so alles abläuft. Der GC war auch mit dabei. Aber auch jede Menge anderer Sachen.
Kein Wunder, dass das Spiel ruckelt wie Sau. Da wundert es mich eher, dass es so viele ruckelfreie Spiele gibt, und was die angestellt haben, damit es ruckelfrei wird. Denn die Hintergrundprozesse können die ja auch nicht einfach ausschalten.

Dein Eintrag klang so, als sollte der GC nicht laufen. Aber das lässt sich doch in Java gar nicht verhindern. Oder?
 
Aber man kann durch geeignete Programmierung dafür sorgen, dass er möglichst wenig zu tun hat.
Wenig temporäre Objekte erzeugen, Daten geeignet cachen, das Flyweight Pattern benutzen, etc etc ...
 
  • Danke
Reaktionen: Duckemai
Duckemai schrieb:
Dein Eintrag klang so, als sollte der GC nicht laufen. Aber das lässt sich doch in Java gar nicht verhindern. Oder?

Verhindern nicht, aber Du kannst einigermassen steuern wann die GC läuft.

Dazu kannst Du die GC manuell anstossen zu Zeitpunkten wo sie Deinem Spielfluss nicht schadet: System.gc(). Am wichtigsten ist es allerdings dass Du selbst die GC nicht beschäftigst. D.h. in Deiner Main-Loop keine Variablen, Speicherblöcke etc. allokieren. Alle Variablen die Du dort verwendest sollten für die Klasse globale Variablen sein, d.h. nicht in einer Methode, und schon gar nicht in Deiner Draw Methode erstellt werden.

Zwei Integer Variablen sind 64 Bit. Das ist nicht viel. Wenn Du die beiden bei jedem Durchlauf Deiner Draw Methode neu erstellst, sind das allerdings 64 Bit Speicher pro Durchlauf die reserviert werden. Wenn Dein Spiel mit 30 fps läuft sind dass dann an die 2k Speicher pro Sekunde.

Sobald Dein Spiel 5 Minuten läuft macht das schon 600k die durch die garbage collection nur durch diese beiden Variablen aufgeräumt werden müssen. Jetzt kannst Du Dir ausrechnen wie das aussieht wenn Du ganze Grafiken allokierst. Da muss die GC dann fast jede Sekunde laufen. Und wenn die Garbage Collection läuft stockt erst mal alles.

Wenn man konsequent in den Spiel-Schleifen auf jegliche Speicherallokation verzichtet und sicherheitshalber die GC noch ab und an selbst anstösst wo es nicht weh tut ruckelt auch nichts.
 
  • Danke
Reaktionen: Duckemai
Hmh! Das klingt doch irgendwie machbar. Vielleicht nicht optimiert wie ein Vollprofi. Aber wenn ich mir einige meiner verschachtelten Schleifen anschaue, dann könnte ich da schon was optimieren.

Schmeiße dabei gleich mal alle Deklarationen in den Schleifen raus und bringe
sie als Klassenvariablen unter, damit sie nur einmal deklariert werden müssen.

Und dann noch das System.gc...

Ich check mal alles bei mir durch und gebe morgen noch mal ein kurzes Feedback.

Vielen Dank!
 
Zuletzt bearbeitet:
Doch! Ich muss sagen, die Bitmaps laufen schon wesentlich ruhiger und das Ruckeln ist fast raus.

Gruß an alle!
 

Ähnliche Themen

S
Antworten
17
Aufrufe
551
jogimuc
J
5
Antworten
22
Aufrufe
1.417
590239
5
M
Antworten
4
Aufrufe
1.169
swa00
swa00
Zurück
Oben Unten