[GELÖST] Max2D - TileImage - Alternativlösung

Übersicht BlitzMax, BlitzMax NG Allgemein

Neue Antwort erstellen

juse4pro

Betreff: [GELÖST] Max2D - TileImage - Alternativlösung

BeitragSo, Nov 10, 2013 6:01
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo,

ich habe beim Arbeiten an meiner GUI aus Performancegründen ein paar Methoden aus Max2D abgeändert. Bisher waren die Änderungen super-minimal.
Jetzt jedoch geht es darum ein Bild bzw. eine Textur wiederholt auf eine rechteckige Fläche zu zeichnen. Eigentlich wäre TileImage() dafür ja perfekt geeignet gewesen. Als ich mir dann aber mal die Funktionsweise im BRL-Ordner angeguckt habe, dachte ich mir: Das geht doch aber bestimmt besser.

Hier mal der essentielle Ausschnitt aus Max2D:
BlitzMax: [AUSKLAPPEN]
Rem
bbdoc: Draw an image in a tiled pattern
about:
#TileImage draws an image in a repeating, tiled pattern, filling the current viewport.
End Rem

Function TileImage( image:TImage,x#=0#,y#=0#,frame=0 )
Local iframe:TImageFrame=image.Frame(frame)
If Not iframe Return

_max2dDriver.SetTransform 1,0,0,1

Local w=image.width
Local h=image.height
Local ox=gc.viewport_x-w+1
Local oy=gc.viewport_y-h+1
Local px#=x+gc.origin_x-image.handle_x
Local py#=y+gc.origin_y-image.handle_y
Local fx#=px-Floor(px)
Local fy#=py-Floor(py)
Local tx=Floor(px)-ox
Local ty=Floor(py)-oy

If tx>=0 tx=tx Mod w + ox Else tx=w - -tx Mod w + ox
If ty>=0 ty=ty Mod h + oy Else ty=h - -ty Mod h + oy

Local vr=gc.viewport_x+gc.viewport_w,vb=gc.viewport_y+gc.viewport_h

Local iy=ty
While iy<vb
Local ix=tx
While ix<vr
iframe.Draw 0,0,w,h,ix+fx,iy+fy,0,0,w,h
ix=ix+w
Wend
iy=iy+h
Wend

UpdateTransform

End Function


Wie man an den 2 geschachtelten Schleifen erkennt, wird das Bild einfach wiederholt gezichnet. Aber da so ziemlich alles in Max2D darauf basiert, texturierte Polygone (meist Quads) darzustellen, kam mir die Idee, einfach ein einziges Quad zu verwenden, statt ganz viele kleine nebeneinander. Normalerweise (bitte korrigiert mich) war es auch in 3D-Modeling-Programmen so, dass wenn eine Textur zu kleinskaliert für ein Polygon ist, diese sich grundsätzlich unendlich-oft wiederholt, oder nicht?
D.h. statt das Bild einfach ganz oft nebeneinander zu zeichnen, würde es doch genügen ein einziges Bild mit eben anderen UV-Variablen zu zeichnen und BAMMS. Fertig.
Und ich hege die leise Vermutung, dass das (vorallem in einer GUI) sehr viele Quads bzw. Draw-Operationen spart. (die internen von Max2D sind gemeint)

Wenn also meine Vermutung jetzt richtig ist, stellt sich mir die Frage, wie ich das ganze angehen sollte. Grundsätzlich habe ich bisher nicht auf so einem Low-Level, also so DirectX oder OpenGL-nah gearbeitet. Und leider sind die Variablen und internen Benennungen unter aller sau.
Vlt. hat ja jemand von euch schon mal solch eine Änderung gemacht?

Wollt' einfach mal hören, was ihr dazu sagt, bzw. ob es Leute gibt, die sich auf diesem Level mit Max2D beschäftigt haben.

PS: Noch genialer wäre natürlich, wenn es alternativ zum DrawSubImageRect() eine Methode gäbe, die exakt das selbe macht, nur ohne diesen doofen Scaling-Effekt. Damit wäre ja quasi das Problem auch gelöst UND man kann, statt immer ein extra Bild / Textur zu nehmen, nur Teile einer größeren Textur zeichnen. (Damit kommen doch Grafikkarten besser klar, nicht wahr? Am besten noch in einer "Power of 2"-Auflösung.) Und das auch noch wiederholt. Durch die Macht der UV-Koordinaten! (*träum*)
Also ich habe ne ganze Weile versucht durch den Low-Level-Code durchzublicken. Aber irgendwie bin ich bisher grandios gescheitert. Very Happy

Wenn hier irgendeine Information fehlerhaft ist, bitte sagen, da das alles Dinge sind, die sich mir selbst so erklärt haben, beigebracht wurde es mir nie. Und was Quellen wie z.B. YouTube und co. anbelangt: Naja, auch dort können Fehler stecken.

Okay genug gelabbert.

Grüße,
Der juse4pro (Dustin)
  • Zuletzt bearbeitet von juse4pro am Di, März 18, 2014 5:05, insgesamt einmal bearbeitet

ZEVS

BeitragSo, Nov 10, 2013 22:27
Antworten mit Zitat
Benutzer-Profile anzeigen
Mir wurde soetwas auch nie beigebracht. Von DirectX verstehe ich überhaupt nichts, mit OpenGL komme ich ein bisschen klar. Ich benutze hin und wieder native OpenGL-Funktionen für 2D-Effekte, die Modulsourcen habe ich aber nie geändert, da sonst das Programm evtl. nur bei mir kompilliert werden könnte (das wäre blöd bei BCC u.ä.). Dies ist aber deine Entscheidung.
Je nach Anspruch (Unterstützung von animierten Bildern, Drehung, Skalierung etc.; OpenGL, DirectX oder beides) kann dies mehr oder weniger aufwändig werden. Für OpenGL müsstest du vom Modul BRL.GLMax2D den Typ TGLImageFrame bearbeiten bzw. von außen manipulieren (ist ja alles public). Dann könntes du eine Methode TileImageGL(...) schreiben, die mit OpenGL arbeitet und effizienter ist. Außerdem kannst du das Modul BRL.GLMax2D im Modul BRL.Max2D einbinden (was zweifellos nicht elegant ist) und dann in TileImage eine Weiche einbauen, die ggf. auf deine Methode zurückgreift (je nach Treiber).

Eine evtl. bessere Alternative besteht darin, dass du die Methode TImageFrame.Draw selbst aufrufst. Soweit ich es bei OpenGL sehe (von DX verstehe ich nichts), kannst du mit den ersten vier Parametern relative Koordinaten angeben, mit dem fünften und sechsten absolute (von denen aus die relativen mit Drehung, Skalierung etc. abgehen) und mit den letzten vier einen UV-Bereich auswählen. Wenn du hier die entsprechenden Parameter wählst, solltest du in der Lage sein, TileImage wesentlich zu vereinfachen. Für die Auswirkungen bei einem DX-Treiber kann ich keine Vermutungen anstellen. Wenn es dort aber ähnlich zugeht, sollte dene Traum-Methode bereits existieren, sie ist nur nicht dokumentiert.

ZEVS
 

Tritium

BeitragDi, Nov 12, 2013 12:19
Antworten mit Zitat
Benutzer-Profile anzeigen
Für OpenGL müsste der entsprechende Befehl
Code: [AUSKLAPPEN]
glTexParameter(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT)
glTexParameter(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT)

oder so lauten.

Gute Beschreibung dazu:
http://wiki.delphigl.com/index.php/glTexParameter

juse4pro

BeitragDo, Nov 14, 2013 0:39
Antworten mit Zitat
Benutzer-Profile anzeigen
@ZEVS:
Von OpenGL hab ich weider kaum Ahnung und würde nur sehr ungern auf eine absolute OpenGL-Basis umsteigen wollen. Ich verstehe, wie du das meinst, finde es aber auch ein bisschen sehr unelegant. Very Happy

Dein Tipp mit dem direkten TImageFrame.Draw()-Aufruf habe ich ausprobiert, aber leider sind die Parameter nicht ganz so, wie du sie hier erklärt hast, jedenfalls hab ich mal durchgerechnet und rumprobiert.

die Parameter sehen so aus:

BlitzMax: [AUSKLAPPEN]
Type TImageFrame

Method Draw( x0#,y0#,x1#,y1#,tx#,ty#,sx#,sy#,sw#,sh# ) Abstract

End Type


Jetzt bin ich nach meinen Tests dazu gekommen, dass die Parameter folgendes beeinflussen:
x0,y0 und x1,y1: Von x0,y0 wird ein Rect gespannt. (Das Source-Rect wird 1:1 auf dieses Rect gepackt, also gibt es eine Skalierung, falls die Breiten und Höhen sich unterscheiden.)
tx,ty: Verschiebt das gespannte Rect nochmal absolut um tx,ty.
sx,sy und sw,sh: Gibt das Rechteck an, welches aus der Textur genommen werden soll.

Eine Vermutung wäre, dass x0,y0 und x1,y1 eventuell doch den Start und Endpunkt des gewählten rechteckigen Ausschnitts aus der Textur definiert, ich aber diesen merkwürdigen Effekt, den ich grade beschrieben habe, nur sehe, weil sozusagen das Wiederholen der Textur deaktiviert wurde. So wie ich z.B. im Code von Tritium sehe, muss man das als Flag mit angeben.
Ist aber nur eine Vermutung.

Irgendwie hab ich das Gefühl, dass hier was mächtig falsch verstehe bzw. mich die Benennung der Variablen so verblendet hat, dass ich gar nicht mehr durchsehe, was jetzt für was steht. Razz

Bitte korrigiert mich, falls ich komplett daneben liege, oder was falsch verstanden habe. Smile

Ich hätte auch kein Problem damit die Module abzuändern (Was am schnellsten gehen würde, tippe ich mal. Ich muss nur den Haken finden.). So viele Programmierer sitzen nicht an diesem Projekt.

ZEVS

BeitragDo, Nov 14, 2013 22:13
Antworten mit Zitat
Benutzer-Profile anzeigen
Zumindest was OpenGL anbelangt, decken sich die Test-Ergebnisse mit meinen Vermutungen. Allerdingt muss man Tritiums Code verwenden, damit wirklich ein Tiling-Effekt zustandekommt. Folgender Code zeigt das Bild explosion.png sechzehn Mal neben- und übereinander, wobei das Gesamtbild ständig gedreht und skaliert wird: BlitzMax: [AUSKLAPPEN]
SuperStrict
SetGraphicsDriver GLMax2DDriver()
Graphics 800, 600
Local img:TImage = LoadImage("explosion.png")
Local timer:TTimer = CreateTimer(60)
Local i:Int = 0
MidHandleImage img
Local frame:TImageFrame = TImageFrame(img.Frame(0))

'Vorbereiten: entfällt hier

glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT
glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT

'Aufräumen: entfällt hier

Repeat

i :+ 1
WaitTimer timer
Cls
SetRotation i
Local s# = 2+Sin(i*3)
SetScale s, s
Local w# = img.width, h# = img.height
frame.Draw(-w/2, -h/2, w/2, h/2, 400, 300, 0, 0, w*4, h*4)
Flip 0

Until KeyHit(KEY_ESCAPE) Or AppTerminate()
End

Zwei Kommentare geben an, dass hier durch die spezielle Wahl der Reihenfolge der Aktionen der Code ohne Vor- und Nachbereiten von Tritiums Code funktioniert. Das liegt daran, dass die OpenGL gerade im Textur-Modus ist (GL_TEXTURE_2D) und auch noch die richtige Textur gebunden ist (dies geschieht in image.Frame(0), was wiederum TGLImageFrame.CreateFromPixmap aufruft). Am einfachsten ist es definitiv, wenn du den Code hier einfügst. Ansonsten musst du aufpassen, ob du den Textur-Modus vorher einschalten (glEnable GL_TEXTURE_2D) und nachher wieder ausschalten musst (glDisable GL_TEXTURE_2D) und ob die Textur bereits gebunden ist (sonst: glBindTexture GL_TEXTURE_2D, frame.name). Das Problem ist das folgende: Wenn du hinterher einen dieser Parameter der OpenGL anders hast als vorher, kann damit GLMax2D durcheinanderkommen. Dieses Modul hat nämlich eigene (private!) Variablen state_boundtex und state_texenabled, von deren Richtigkeit es ausgeht. Wenn GLMax2D z.B. ein Rechteck zeichnen will, würde es glDisable GL_TEXTURE_2D aufrufen, da keine Textur gerendert werden soll. Wenn gl_texenabled aber den Wert False hat, spart es sich diesen Aufruf. Falls du aber die Texturen inzwischen eingeschaltet hast, um den Tiling-Effekt zu aktivieren, siehst du statt des Rechtecks nichts oder irgendein Bild. Für dieses Problem gibt es drei Lösungen:
1. Du schreibst deine Funktion in den Source-Code von GLMax2D und kannst dann sogar die einfachere Hilfsfunktion EnableTex frame.name vorher aufrufen.
2. Du überprüfst mit glIsEnabled(GL_TEXTURE_2D), ob die Texturen eingeschaltet sind und mit BlitzMax: [AUSKLAPPEN]
Local currentTexture:Int
glGet(GL_TEXTURE_BINDING_2D, Varptr currentTexture)
, welche Textur gerade gebunden ist (und hinterher wieder gebunden werden muss).
3. Du rufst vorher eine bestimmte Graphik-Funktion auf, z.B. Plot -1, -1. Dann kannst du dir sicher sein, dass die Texturen ausgeschaltet sind und auch keine gebunden ist, sodass BlitzMax: [AUSKLAPPEN]
Plot -1, -1
glEnable GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D, frame.name
'...
glEnable GL_TEXTURE_2D
funktioniert.

Zur absoluten OpenGL-Basis: Wenn man aus meinem Codebeispiel die gl-Zeilen entfernt, hat man bei allen Graphik-Treibern das gleiche Ergebnis, also keine Tiles sondern blöde Striche. Evtl. gibt es für Tritiums Code eine analoge Konstruktion in DirectX, die das Tiling aktiviert. Davon verstehe ich aber wie gesagt nicht.

ZEVS

juse4pro

BeitragDi, März 18, 2014 5:05
Antworten mit Zitat
Benutzer-Profile anzeigen
Habe die Lösung gefunden.

Habe für die OpenGL-Variante deinen Vorschlag entgegengenommen und im GL-Driver, überall wo der Wrapping-Mode per glTexParameteri auf "GL_CLAMP" (abschneiden) gesetzt war, mit "GL_WRAP" ausgetauscht.

Für die DirectX-Variante geht das eben so leicht, nur, dass der Modus für die Texturen hier nicht auf die Texturen, sondern für das gesamte Device, bzw. die Rasterization-Stage der GPU-Pipeline gesetzt ist. Unter DirectX 7 und 9 bekannt als "Texture Adressing" (das selbe, wie das GL-Wrapping).
Einfach diesen Teil:
BlitzMax: [AUSKLAPPEN]
SetTextureStageState 0,D3DTSS_ADDRESS,D3DTADDRESS_CLAMP

Austauschen mit:
BlitzMax: [AUSKLAPPEN]
SetTextureStageState 0,D3DTSS_ADDRESS,D3DTADDRESS_WRAP


Gilt für DirectX 7 und 9 gleichermaßen.
Jetzt muss ich mir nur noch ne Funktion bauen, die die UV-Koordinaten so rechnet, wie ich sie brauche. Vielleicht schreibe ich auch die TileImage-Funktion direkt um. An sich ist das Problem aber gelöst. Danke für die Denkanstöße. Smile

Falls ich eine passende Funktion geschrieben habe, werde ich sie hier rein-editieren.

Edit 1:
Bedenkt, dass sowohl OpenGL und DirectX eure Bilder in Texturen umwandeln. D.h. dass die Grafikkarte diese Texturen immer in eine 2^n Auflösung bringt, um diese Texturen effizienter zu speichern und zu verwalten (keine Sorge: Keine Zerrung). Aber ich habe z.B. grade ein 70x70 großes Bild geladen und habe mich über große transparente Lücken gewundert. Die aus dem Bild entstandene Textur war dann im Endeffekt 128x128 Texel groß (http://en.wikipedia.org/wiki/Texel_(graphics)).
D.h. ihr solltet sowieso, um auch speichereffizienter zu arbeiten, immer in 2^n Auflösungen arbeiten. Auf heutigen Geräten merkt man zwar kaum einen Unterschied, vor allem für 2D-Games, aber die Summe macht's. Wink
Mit einer 2^n Textur braucht die Grafikkarte kein Restplatz zu belegen und die Texturen passen nahtlos aneinander.
Achja und: Das Bild muss nicht quadratisch sein. D.h. es sind auch Auflösungen von z.B. 256x1024 möglich.

Edit 2:
Hier mal ein kleiner knackiger Versuch für eine solche Funktion, wie ich sie für meine GUI benötige:
ZEVS's Code, abgeändert.
BlitzMax: [AUSKLAPPEN]
SuperStrict
SetGraphicsDriver D3D9Max2DDriver()
Graphics 800, 600
SetClsColor 60, 140, 200
SetBlend(ALPHABLEND)

Local img:TImage = LoadImage("test.png")
Local timer:TTimer = CreateTimer(60)


Function DrawAreaImage(imageReference:TImage, sx:Float, sy:Float, dx:Float, dy:Float, frameIndex:Int = 0)
Local frame:TImageFrame = imageReference.Frame(frameIndex)
Local width:Float = dx-sx
Local height:Float = dy-sy
frame.Draw(sx, sy, dx, dy, 0, 0, 0, 0, width, height)
EndFunction

Repeat
WaitTimer timer
Cls
DrawAreaImage(img, 80, 80, MouseX(), MouseY())
Flip 0
Until KeyHit(KEY_ESCAPE) Or AppTerminate()
End

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Allgemein

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group