Wie konfiguriere ich meinen GLES-Frame-Puffer nach einer Drehung neu?

7

Hinweis: Um die Frage so einfach wie möglich zu halten, ändere ich aktiv den Hauptteil der Frage, um den aktuellen Stand der Dinge widerzuspiegeln, anstatt unten etwas hinzuzufügen.

Ich implementiere die Schnittstellenrotation für mein GLES-basiertes Spiel für iOS, das in Xamarin.iOS mit OpenTK geschrieben wurde.

Ich erkenne die Drehung durch Überschreiben WillRotatein meinem UIViewControllerund richte alle meine Projektionsmatrizen korrekt neu ein.

Die Ergebnisse beim Zeichnen eines Sprites im Quer- und Hochformat unterscheiden sich jedoch geringfügig - die Landschaft ist etwas verschwommen -.

Dies ist das von mir verwendete Test-Sprite (128 x 128), um Rundungsfehler zu vermeiden:

Beispielbild

Meine Testprojektionsmatrix basiert auf einem 1024x768-Display für ein iPad. Obwohl ich das Bild nicht skalieren werde, verwende ich die Punktfilterung, daher erwarte ich, dass das Rendering pixelgenau ist. Die Renderposition ist erneut (0, 0), um Rundungsfehler zu vermeiden. Das Sprite sollte sowohl im Hoch- als auch im Querformat genau in die Pixelgrenzen fallen.

Außerdem verwende ich in beiden Fällen dieselbe Textur mit demselben Sampler, demselben Shader und demselben GL-Status.

Dies passiert, wenn ich das Sprite im Hochformat rendere. So erwarte ich, dass es gerendert wird (5-fach vergrößert):

Porträt original

Und genau das passiert, wenn ich dasselbe Sprite im Querformat rendere.

Landschaft Original

Ich glaube, dass der Framebuffer als 768x1024 erstellt wurde, aber wenn er gedreht wird, passt er auf den 1024x768-Bildschirm, ist horizontal gequetscht und vertikal gestreckt, und dies ist der Grund für das Problem.

Wenn ich OpenTK so einstelle, dass ein 1024x1024-Framebuffer erstellt wird, wird das Ergebnis im Hoch- und Querformat konsistent. Das ist Porträt:

Porträt 1024

Beachten Sie, dass es in der Vertikalen keine Verzerrung gibt, die ebenfalls 1024 Pixel beträgt. Die Verzerrung ist auf die Horizontale beschränkt, wo die Auflösung nicht übereinstimmt. Das ist Landschaft:

Landschaft 1024

Diesmal bewegte sich die Verzerrung in die Vertikale, was meine Hypothese stützte.

Wenn Sie es bei 3072 x 3072 erstellen, wobei 3072 das am wenigsten verbreitete Vielfache von 1024 und 768 ist, sind die Ergebnisse sowohl im Hoch- als auch im Querformat korrekt:

3072 x 3072

Beachten Sie, dass dies keine Lösung ist, da ich nicht nur viel Speicher verschwenden, sondern auch inkonsistente Multisampling-Artefakte mit 3D erzeugen werde, da sich die vertikale Skalierung von der horizontalen Skalierung unterscheidet. Darüber hinaus ist dies keine Option für das iPhone 5, für dessen Auflösung von 1136 x 640 ich einen Framebuffer von 45440 x 45440 erstellen müsste.

Da eine Gerätedrehung als Fenstergrößenänderung angesehen werden kann, muss der Framebuffer meiner Meinung nach neu erstellt werden, um der neuen Größe zu entsprechen. Dies ist für Windows unter Direct3D erforderlich. Ich müsste anrufen swapChain.ResizeBuffers(), um dies in SharpDX zu tun.

Ich habe versucht, AutoResize = truemeine Einstellungen vorzunehmen iPhoneOSGameView, aber dann wird der Framebuffer beim Drehen der Benutzeroberfläche abgeschnitten, und beim erneuten Drehen der Benutzeroberfläche verschwindet alles.

Ich mache nichts Seltsames, meine Framebuffer-Initialisierung ist ziemlich vanille:

int scaling = (int)UIScreen.MainScreen.Scale;
DeviceWidth = (int)UIScreen.MainScreen.Bounds.Width * scaling;
DeviceHeight = (int)UIScreen.MainScreen.Bounds.Height * scaling;
Size = new System.Drawing.Size((int)(DeviceWidth), (int)(DeviceHeight));
Bounds = new System.Drawing.RectangleF(0, 0, DeviceWidth, DeviceHeight);
Frame = new System.Drawing.RectangleF(0, 0, DeviceWidth, DeviceHeight);
ContextRenderingApi = EAGLRenderingAPI.OpenGLES2;
AutoResize = true;
LayerRetainsBacking = true;
LayerColorFormat = EAGLColorFormat.RGBA8;

Ich erhalte inkonsistente Ergebnisse bei einem Wechsel Size, Boundsund Frameauf meiner CreateFrameBufferaußer Kraft setzen. Leider ist die Dokumentation sehr unvollständig, so dass ich hier und da auf zufällige Änderungen zurückgegriffen habe, ohne wirklich zu wissen, was los ist.

Es gibt eine ähnliche Frage, die keine Antworten hat. Ich weiß jedoch nicht, ob sie das gleiche Problem haben wie ich.

In den Xamarin-Foren gibt es ähnliche Bedenken, insbesondere in Bezug auf das Rotationsproblem. Es scheint keine Lösung zu geben, außer GameViewvollständig fallen zu lassen.

Ist meine Vermutung, dass die Neuerstellung des Framebuffers notwendig ist, richtig? Wenn ja, weiß jemand, wie man es in OpenTK für Xamarin.iOS richtig macht?

Panda Pyjama
quelle
2
Nur eine Randnotiz, vielleicht könnten Sie mit einem 1px Schachbrettmusterbild überprüfen, um besser zu sehen, was los ist (Upscaling, Downsampling, Texel Shift usw.)
Kromster
@Krom: Ich kann das tun
Panda Pyjama
1
Was passiert, wenn Sie keine Gerätedrehung verwenden, sondern eine 90 ° -Drehmatrix anwenden? Wird dies die gleiche Verzerrung bewirken?
Bogglez
@ Bogglez Ich kann das auch
Panda Pyjama
@Krom: Ich habe weitere Informationen hinzugefügt, die für Ihre Kommentare relevant sind
Panda Pyjama

Antworten:

1

Ja, der Renderpuffer muss beim Drehen der Benutzeroberfläche neu erstellt und auf die neue Größe eingestellt werden.

Es ist nicht schwierig, es neu zu erschaffen. Die Renderbuffer-ID ist jedoch privat iPhoneOSGameViewund nur über die RenderbufferEigenschaft zugänglich , die keinen öffentlichen Setter hat.

Reflexion zur Rettung! Bei der Inspektion iPhoneOSGameViewstellte ich fest, dass es ein Feld enthält renderbuffer, das als Hintergrundfeld dahinter vielversprechend erscheint Renderbuffer.

Wenn ich also eine Drehung erkenne, erstelle ich meinen Renderpuffer neu und setze ihn dann über Reflektion.

Solche Dinge zu tun ist ein bisschen riskant, da ich nicht wirklich weiß, was ich sonst (wenn überhaupt) ändern muss, aber bisher sieht es gut aus.

public void ResizeFrameBuffer(int newWidth, int newHeight)
{
    DeviceWidth = newWidth;
    DeviceHeight = newHeight;

    Size = new System.Drawing.Size(DeviceWidth, DeviceHeight);
    Bounds = new System.Drawing.RectangleF(0, 0, DeviceWidth, DeviceHeight);

    MakeCurrent();

    int renderbuffer = Renderbuffer;

    GL.DeleteRenderbuffers(1, ref renderbuffer);

    GL.BindFramebuffer(FramebufferTarget.Framebuffer, Framebuffer);
    GL.GenRenderbuffers(1, out renderbuffer);
    GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, renderbuffer);
    EAGLContext.RenderBufferStorage((uint)RenderbufferTarget.Renderbuffer, (CAEAGLLayer)Layer);
    GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferSlot.ColorAttachment0, RenderbufferTarget.Renderbuffer, renderbuffer);
    GL.Viewport(0, 0, DeviceWidth, DeviceHeight);
    GL.Scissor(0, 0, DeviceWidth, DeviceHeight);

    var type = typeof(iPhoneOSGameView);
    var renderbufferField = type.GetField("renderbuffer", BindingFlags.NonPublic | BindingFlags.Instance);
    renderbufferField.SetValue(this, renderbuffer);

    Frame = new System.Drawing.RectangleF(0, 0, DeviceWidth, DeviceHeight);
}

iPhoneOSGameView habe es nie kommen sehen ...

Panda Pyjama
quelle
0

Dies ist jetzt Teil von OpenTK. iPhoneOSGameViewhat jetzt eine ResizeFrameBufferMethode, die auf der Antwort von @Panda Pyjama basiert und bei der Bildschirmrotation aufgerufen werden sollte.

https://github.com/opentk/opentk/pull/521

tzachs
quelle