COOKIES

This site may be using cookies to melk you with your own data. I, ben0bi, am not the owner of this web service and I also do not maintain their servers. But the EU and the owner of this service think, that the user (me) has the responsibility to inform the consumer (you), that this website uses cookies. Again: I, ben0bi, NEVER use cookies. I am not responsible for the setup of this web service. I just present some information here and do not intend to spy on you for whatever reason ever. But (also again), I do not host this website nor do I maintain any servers related to this website nor do I benefit from using the cookies maintained from this service. I hereby give the responsibility for using cookies on blogspot back to the owners of blogspot.
Posts mit dem Label c# werden angezeigt. Alle Posts anzeigen
Posts mit dem Label c# werden angezeigt. Alle Posts anzeigen

Samstag, 19. Oktober 2019

DIY Emulator Teil 2.1.1: Refining EmuGraphicsAdapter

DIY Emulator Teil 2.1.1: Refining EmuGraphicsAdapter

in Unity

Nachdem ich in diesem Artikel gelernt habe, dass schon kleinste Änderungen am Code enorm mehr Speed herausholen können, werden nun erstmal ein paar Speed-Optimierungen gemacht.

(Ich mache dann (eventuell :) ) schon mal einen "richtigen" Emulator aber ich muss erst noch rausfinden, wie man einen emulierten (schon programmierten) Chip überhaupt einbaut und benutzt und davor hab ich schon ein bisschen Bammel weil ich wirklich absolut keine Ahnung davon habe. Bitte entschuldigt das andauernde Aufgeschiebe. Diese Tutorials sollen ja auch für mich sein, nicht nur für dich... ;) )

Darum wurde dort die MarkPixelToChangeByIndex-Funktion im EmuGraphicsAdapter (der Klasse aus diesem Artikel) hinzugefügt.

Um noch mehr Speed herauszuholen - denn millionenfache Funktionsaufrufe brauchen dann doch ein bisschen Zeit - werden wir nun noch eine Funktion generieren, die gleich eine gesamte Map (Display) mit der zugehörigen Palette direkt auf der Textur verrechnet, ohne dazu immer extra pro Pixel eine Funktion aufrufen zu müssen.*

[edit]: Wenn dein Emulator Display genau gleich gross ist, wie die Textur und auf 0,0 positioniert wird (also keine "hidden Pixel" hat), ist MarkPixelToChangeByIndex immer noch ein bisschen schneller als die folgende Funktion.*

 [edit 2]: Ich bin grüble darüber nach, ob und wie man das mit Shadern "richtig schnell" machen kann. Anders gesagt: Auch wenn hierbei kein Emulator rauskommt, hast du dann trotzdem eine richtig flotte 2D-Pixelzeichungs-Engine parat.

*Desweiteren wird diese Funktion auch das Display des Emulators "korrekt" auf der Textur platzieren:
Einige Systeme haben einen grösseren Display-Speicher als auf dem Bildschirm angezeigt. Eine oder mehrere Linien oder Zeilen sind ausserhalb des sichtbaren Bereiches. Diese "hidden pixel" werden vor allem bei Scrolling-Funktionen benutzt, damit es kein Geflacker gibt.

Wir könnten entweder die Texturgrösse anpassen, oder einfach diese Pixel auslassen. Ich habe mich erstmal für zweiteres entschieden.

Schlussendlich gibt es noch einen paletteModifier, so dass man nicht immer vorher die gesamte Map mit neuen Indexen aufbauen muss, sondern nur diesen Modifier verändert. (Wird durch den plasmaIndex im Placeholder_Emulator bestimmt.)

Also, hier die Funktion: MarkPalettedMapToChange1D in der EmuGraphicsAdapter-Klasse
Die Funktion hat ziemlich viele Parameter, Sorry dafür.

  /* Parameters:
     * map: 1-dimensional array with palette indexes.
     * mapwidth: width (max-x) of the map to make it 2-dimensional.
     * mapheight: height (max-y) of the map to make it 2-dimensional.
                  mapheight can be calculated in-function but that wastes processing power.
     * palette: 1-dimensional array with colors arranged in the right order.
     * paletteModifier: add this modifier to the palette index on the map to get the right color.
     *                  used to not create the whole map with each palette change.
     * posX: start-position of the map on the display. used for displays with "hidden pixels".
     * posY: start-position of the map on the display. used for displays with "hidden pixels".
     */
    // define the variables once instead of in each function call.
    private int tmp_texx, tmp_texy, tmp_texidx, tmp_emux, tmp_emuy, tmp_emuidx;
    private int tmp_palsize, tmp_palindex;
    private int tmp_spritewidth, tmp_spriteheight, tmp_mapsize;
    public void MarkPalettedMapToChange1D(int[] map, int mapwidth, int mapheight,
                                          Color[] palette, int paletteModifier = 0,
                                          int posX = 0, int posY = 0)
    {
        // get spritewidth and height.
        tmp_spritewidth = (int)m_drawable_sprite.rect.width;
        tmp_spriteheight = (int)m_drawable_sprite.rect.height;
        tmp_mapsize = map.Length;
        tmp_palsize = palette.Length;
        // go through x and y instead of only the index.
        // for better calculation of the emulator-display-position.
        // go through the DISPLAYED texture array:
        for(tmp_texy=0;tmp_texy < tmp_spriteheight;tmp_texy++)
        {
            tmp_emuy = tmp_texy - posY;
            // y not in bounds, break or continue.
            if (tmp_emuy < 0) // < 0 = continue
                continue;
            if(tmp_emuy >= mapheight) // > height = break
                break;
            for (tmp_texx = 0; tmp_texx < tmp_spritewidth; tmp_texx++)
            {
                // calculate position on the EMULATOR display:
                tmp_emux = tmp_texx - posX;
                // x is not in bounds, break or continue.
                if (tmp_emux < 0)
                    continue; // continue if smaller.
                if (tmp_emux >= mapwidth)
                    break; // break if bigger.

                // calculate the real index on the map.
                // because x and y are in boundsl, we do not need to check the index itself.
                tmp_emuidx = (int)((tmp_emuy * mapwidth) + tmp_emux);

                // get the palette index
                tmp_palindex = map[tmp_emuidx] + paletteModifier;
                // maybe we need to adjust the palette index to get it into bounds.
                while (tmp_palindex >= tmp_palsize)
                    tmp_palindex -= tmp_palsize;

                // finally set the new color.
                tmp_texidx = tmp_texy * tmp_spritewidth + tmp_texx;
                m_cur_colors[tmp_texidx] = palette[tmp_palindex];
            }
         }
    }

Alle tmp-Variablen werden im Scope der Klasse erstellt, damit sie nicht bei jedem Aufruf der Funktion neu erstellt werden müssen. Die Parameter der Funktion sind die folgenden:
  • map: 1-dimensionales Array mit Palette-Indexen.
  • mapwidth: Breite einer Zeile der map (x)
  • mapheight: Höhe der map (y) (Könnte man auch in der Funktion jedesmal neu berechnen, was jedoch unnötig Prozessorpower verbraucht.)
  • palette: 1-dimensionales Array mit den Farben (Color) in der richtigen Reihenfolge.
  • paletteModifier: Addiere diese Zahl zu jedem Index auf der map.
  • posX: Startposition der map auf der Textur in Pixeln (x).
  • posY: Startposition der map auf der Textur in Pixeln (y).
Es wird, obwohl die Arrays eindimensional sind, trotzdem 2-dimensional durch das Textur-Array hindurch gegangen. Dabei wird jeweils die Position des Pixels auf der map berechnet, und ob diese Position überhaupt auf der map vorhanden ist. Dazu braucht es mapwidth und mapheight. Wenn x oder y auf der map kleiner als 0 ist, wird die Schleife mit continue sofort fortgesetzt, wenn die jeweilige Variable jedoch zu gross ist, wird die zugehörige Schleife sofort mit break gleich ganz verlassen. PS: Einige Checks habe ich wieder herausgenommen, da sie nur unnötig Prozessorzeit verbrauchten und nie "erfüllt" wurden. Dies hier ist die aktuellste Version.

Nun können wir noch die copyDisplay-Funktion im Placeholder_Emulator ändern:

    // copy the paletted display colors to the real color array.
    protected void copyDisplay()
    {
        // alt: for(q = 0; ...
        gfx.MarkPalettedMapToChange1D(m_display, disp_width, disp_height,
                                      m_palette, (int)m_plasmaIndex, 0, 0);
    }

Uuund....weiteres folgt bald. Eventuell.

Das Projekt befindet sich in diesem Git-Repository, im JUMPEE-Verzeichnis.

Mittwoch, 16. Oktober 2019

DIY Emulator Teil 2.1: Der Placeholder-Emulator

DIY Emulator Teil 2.1: Der Placeholder-Emulator

in Unity


(Interlude - Dieser Artikel ist für den weiteren Verlauf der Tutorials nicht relevant.)

Du solltest das Framework des vorigen Artikels nun aufgesetzt haben...

Nachdem wir in den vorigen zwei Artikeln ein kleines 2D-Framework für Emulatoren in Unity3D aufgesetzt haben, soll dieses Framework nun auch einmal etwas anzeigen.

Wie schon beschrieben ist der Placeholder-Emulator kein wirklicher Emulator sondern hat nur die selbe Klasse als Basis. Der Placeholder-Emulator läuft im Hauptmenü, wenn kein anderer Emulator geladen ist. Dies dient der Vereinfachung: Im System ist immer ein Emulator am laufen und somit muss kein Extracode für den Fall geschrieben werden, wenn eben kein Emulator geladen ist. Desweiteren kann man hier verschiedenste Sachen austesten, ohne gross etwas kaputt zu machen.

In diesem Artikel werden wir einen Plasma-Effekt generieren.

Dazu wird eine Farbpalette erstellt.
Dann wird das Display des Emulators mit zufälligen Indexen dieser Palette gefüllt.
Danach wird das Display "refined", also alle Pixel werden vom Wert her an die benachbarten angeglichen. Das gibt schöne "Hügel und Täler".

Dieses Bild aus Paletten-Indexen dient nun als Ausgangsbasis.

In Jedem Frame werden die Paletten-Indexe des Displays als Farbe (Color) aus der Palette geholt und auf die Textur "gemalt". Dabei wird ein "plasmaIndex" dazu gerechnet, welcher das Plasma...plasmieren lässt.

Alles was man pro Frame ändern muss ist der plasmaIndex, und dann natürlich noch das Display in Colors umrechnen.

Wir nehmen den Placeholder-Emulator und erweitern diesen (mehrere Abschnitte):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Placeholder_Emulator : EmulatorBase
{
    protected int[] m_display; // pos = y * disp_width + x
    protected Color[] m_palette;
    protected int m_palSize = 0xFF; // palsize is one byte. :)

    // this index is added to the actual map palette index to "move" the colors.
    protected float m_plasmaIndex = 0.0f;

    // we need to slow down the things a little.
    protected int colorsPerSecond = 20;

    public Placeholder_Emulator(GameObject g) : base(g)
    {
        Debug.Log("EPlaceholder_Constructor");
        disp_width = 320;
        disp_height = 240;
        m_display = new int[disp_width * disp_height];

        createPalette();
        createPlasmaField();
    }

    public override void Update()
    {
        copyDisplay();

        m_plasmaIndex+=Time.deltaTime*colorsPerSecond;
        if ((int)m_plasmaIndex >= m_palSize)
            m_plasmaIndex = (float)m_palSize-m_plasmaIndex;
    }

Der Placeholder_Emulator ist von der vorher erstellten EmulatorBase abgeleitet.
  • m_display ist das der Textur entsprechende Array mit den Palette-Indexen für das Plasma-Bild.
  • m_palette ist das Array mit den Colors für die Indexe in m_display.
  • m_palSize ist die Grösse der Palette.
  • m_plasmaIndex ist der aktuelle Index, welcher auf jeden Index in m_display aufgerechnet wird.
  • colorsPerSecond zeigt an, wieviele Farbwechsel (Änderung von m_plasmaIndex) es pro Sekunde geben soll.
  • Im Konstruktor wird das GameObject (von EmuCreator) an den Basis-Konstruktor übergeben.
    Die Grösse des Displays wird auf 320x240 (PC-SCREEN 13) eingestellt und m_display wird initialisiert. Dann werden die Palette und das Plasmabild erstellt mit createPalette und createPlasmaField.
  • Schliesslich wird in jedem Update das Display per copyDisplay auf die Textur "gerendert". Dann wird der plasmaIndex um frameTime * colorsPerSecond erhöht.

createPalette()

Erst mal die createPalette-Funktion:

    // create a palette.
    protected void createPalette()
    {
        m_palette = new Color[m_palSize];

        float r = 0.0f;
        float g = 0.0f;
        float b = 0.0f;
        float eight = m_palSize / 8.0f;
        for(int i=0;i < m_palSize;i++)
        {
            // first red to yellow
            if (i<=eight)
            {
                r = 1.0f;
                g = 1.0f / eight * i;
                b = 0.0f;
            }

            // not to white because it's to bright.
            // then yellow to black
            if(i>eight && i<=eight*2)
            {
                r = 1.0f - (1.0f / eight * (i - (eight * 1))); // reverse;
                g = 1.0f - (1.0f / eight * (i - (eight * 1))); // reverse;
                b = 0.0f;
            }

            // then black to turkis
            if (i > eight*2 && i <= eight * 3)
            {
                r = 0.0f;
                g = 1.0f / eight * (i - (eight * 2));
                b = 1.0f/eight*(i-(eight*2));
            }

            // then turkis to green
            if (i > eight * 3 && i <= eight * 4)
            {
                r = 0.0f;
                g = 1.0f;
                b = 1.0f - (1.0f / eight * (i - (eight * 3)));  
            }

            // then green to black
            if (i > eight * 4 && i <= eight * 5)
            {
                r = 0.0f;
                g = 1.0f - (1.0f / eight * (i - (eight * 4))); // reverse
                b = 0.0f;
            }

            // then black to blue
            if (i > eight * 5 && i <= eight * 6)
            {
                r = 0.0f;
                g = 0.0f;
                b = 1.0f / eight * (i - (eight * 5));
            }

            // then blue to magenta
            if (i > eight * 6 && i <= eight * 7)
            {
                r = 1.0f / eight * (i - (eight * 6));
                g = 0.0f;
                b = 1.0f;
            }

            // then magenta to red
            if (i > eight * 7)
            {
                r = 1.0f;
                g = 0.0f;
                b = 1.0f - (1.0f / eight * (i - (eight * 7))); // reverse
            }

            m_palette[i] = new Color(r, g, b);
        }
    }


Diese Palette geht durch alle Farben ausser Weiss (das war zu hell, ich habe es durch Schwarz ersetzt. Rate, wo. ;) ). Darum braucht es acht Abschnitte. Die Variable eight ist ein Achtel der Gesamtpalette. So kann man in jedem Unterabschnitt mit eight und dem Index von 0.0f bis 1.0f oder umgekehrt gehen.

Zunehmende Farbe ist: 1.0f/eight * i
Abnehmende Farbe ist: 1.0f-(1.0f/eight * i)

Dazu muss man von i jeweils noch ein paar Achtel abziehen, damit i immer im Bereich von 0 bis eight ist.

Die Reihenfolge der Farben ist die folgende:  Rot, Gelb, Schwarz, Türkis, Grün, Schwarz, Blau, Magenta, Rot

createPlasmaField()

Nun die createPlasmaField-Funktion:
Erst wird das gesamte Display mit Zufallswerten von 0 bis zur Palettengrösse gefüllt.
Dann wird vier mal durch das Display durchgegangen. Die umliegenden und der aktuelle Pixel werden zusammengezählt und der Durchschnitt des Ergebnisses ausgerechnet. Der Dividor für den Durchschnitt wird jedesmal neu berechnet, da es am Rand weniger Pixel hat.

    protected void createPlasmaField()
    {
         // initialize the display array.
         int m_display = new int[disp_width * disp_height];
         // fill the display with random values.
         for(int i=0;i<m_display.Length;i++)
              m_display[i] = (int)Random.Range(0, m_palSize);

         // go several times through the whole display and
         // smoothen the pixel color(-indexes)
         // 4 steps are appropriate: less do it carvy, more flatten it out to one color.
         int dividor = 0;
 
         int newcol = 0; // color is a palette index, not a color.
         for(int steps=0;steps < 4;steps++)
         {
             // go through x and y instead of mapIndexes because...
             for(int y = 0;y < disp_height; y++)
             {
                for(int x = 0;x < disp_width; x++)
                {
                    dividor = 0;
                    // .. we need to get the right position here.
                    for(int yp= -1;yp<=1;yp++)
                    {
                       for(int xp= -1;xp<=1;xp++)
                       {
                          // get x and y of the pixel to add
                          int newx = x + xp;
                          int newy = y + yp;
                          // check if it is in bounds.
                          if(newx >= 0 && newx < disp_width &&
                             newy >= 0 && newy < disp_height)
                           {
                              dividor++;
                              int idx = newy*disp_height + newx;
                              newcol += m_display[idx];
                           }   
                       }
                    }
                    // divide by dividor.
                    newcol = (int)newcol / dividor;
                    // set new, "smooth" palette index.
                    m_display[y*disp_width +x]=newcol;
                }
             }
         }
    }

In dieser Funktion hat es ziemlich viele for-s, doch das macht nichts, da sie nur einmal aufgerufen wird. Bei Update allerdings musste ich mehrmals "drüber", da schon kleinste Multiplikationen (zB. index aus x,y ausrechnen pro Pixel) einen enormen Einfluss auf den Verarbeitungs-Speed haben (bei einem Array in der Grösse eines Displays). Im EmuGraphicsAdapter hat es dafür eine neue Funktion MarkPixelToChangeByIndex. Eventuell kommt später noch eine "CopyPaletteToTexture(map, palette)" in den EmuGraphicsAdapter, um nur einen Funktions-Call zu machen statt xTausend, doch bis jetzt reicht das.

Was genau gemacht wird, ist oben schon erklärt. Mit den innersten Schleifen werden die Pixel um x und y herum ausgelesen.

copyDisplay():

Schliesslich noch die copyDisplay-Funktion. Da das Display hier gleich gross ist wie die Textur, kann man direkt mit dem Index arbeiten.

    protected void copyDisplay()
    {
       // speed up the things a little with direct indexing.
       for(int i=0; i &lt m_display.Length; i++)
       {
          gfx.MarkPixelToChangeByIndex(i, m_palette[m_display[i]]);
       }
    }
} // end the class here.

Die vielen MarkPixelToChange-Aufrufe pro Frame sind nicht so der Bringer, das wird noch refined.

Desweiteren muss ja eigentlich nicht in jedem Frame das Bild neu gelöscht, aufgebaut und angezeigt werden, sondern nur wenn sich der plasmaIndex ändert (also alle 3 Frames, zur Zeit: 20 Änderungen auf 60 Frames pro Sekunde verteilt). Dazu müsste man switchBuffers aus dem EmuGraphicsAdapter-Update herausnehmen und im Emulator selbst aufrufen.

Ich lerne das Zeug auch alles gleich neu, da wird eventuell noch einiges geändert in älteren Artikeln.
[änderungen folgen] ;)

Jetzt musst du noch.....Play drücken. :)

Das Projekt findest du auch auf github, und zwar hier:
https://github.com/ben0bi/EmulatroniX
im Ordner "JUMPEE".

JUMPEE heisst "JavaScript & Unity Multi Purpose Emulator Environment"
naja, J müsste man rausnehmen, es ist ja jetzt C#. Aber dann tönts nicht mehr so gut....

Den Plasmaeffekt in JavaScript findest du zum angucken hier:
https://ben0bi.github.io/EmulatroniX

Dienstag, 15. Oktober 2019

DIY Emulator Teil 2.1: Plugin-Emulator (UNITY)

DIY Emulator Teil 2.1: Plugin-Emulator-Framework

in Unity

Nachdem wir in diesem Artikel ein grafisches Grundgerüst aufgesetzt haben, wird nun hier das Grundgerüst für die Emulatoren selbst aufgesetzt.

Die Applikation soll mehrere Emulatoren unterstützen können und mit "Plugins" aufgerüstet werden können. Um die Dinge zu vereinfachen, wird "immer" ein Emulator laufen, es gibt keinen spezifischen Hintergrund für die Applikation selbst. Jegliche Grafik bis auf das UI wird durch einen "Emulator" dargestellt. Das heisst, dass, während KEIN Emulator geladen ist, ein "Platzhalter-Emulator" läuft, welcher eine beliebige Grafikausgabe machen kann und auch nicht wirklich ein Emulator ist. Er hat nur die selbe Basisklasse wie ein Emulator.

Dazu gibt es mehrere Scripts:
  • Ein EmuCreator-Script, welches den Emulator auswählt, erstellt und initialisiert. Dieses wird an das GameObject mit dem EmuGraphicsAdapter (das Script aus dem vorigen Artikel) angehängt.
  • Eine Emulator-Basis von welcher alle Emulator-Klassen abgeleitet sind.
  • Eine abgeleitete Emulator-Klasse zu Testzwecken und später als Platzhalter-Emulator.
Erstellen wir also erstmal den EmuCreator und hängen dieses Script an das GameObject an. (In der Szene sollte nur ein GameObject vorhanden sein bis jetzt.)

EmuCreator.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(EmuGraphicsAdapter))]
public class EmuCreator : MonoBehaviour
{
    protected EmuGraphicsAdapter gfx;
    protected EmulatorBase m_emulator=null;

    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("EmuCreator started.");
        gfx = gameObject.GetComponent<EmuGraphicsAdapter>();

        // create an emulator just for testing.
        createEmulator(new Placeholder_Emulator(gameObject));
    }

    // Update is called once per frame
    void Update()
    {
        if (m_emulator!=null)
            m_emulator.Update();
    }

    // create an emulator and initialize it.
    void createEmulator(EmulatorBase emu)
    {
        m_emulator = emu;
        gfx.setEmuScreenSize(emu.getDisplayWidth(), emu.getDisplayHeight());
    }
}

  • gfx ist die Referenz auf die Graphik-Komponente, welche im vorigen Artikel erstellt wurde.
  • m_emulator ist die Instanz des aktuell laufenden Emulators.
Da dieses Script an das GameObjekt angehängt wird, werden Start und Update hier automatisch aufgerufen. Beim Emulator selbst gibt es keine Start-Funktion sondern einen Konstruktor und Update  muss von "hier" aus manuell ausgeführt werden.

Mit createEmulator(emulator) wird m_emulator gesetzt und dann die Bildschirmgrösse auf das Emulatordisplay angepasst.

Mehr braucht es hier gerade nicht. In Start wird der Platzhalter-Emulator gestartet, welcher im übernächsten Script beschrieben wird. Doch dafür brauchen wir erst die EmulatorBase-Klasse, welche als Basis für alle unsere Emulator-Plugins dienen wird.

EmulatorBase.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EmulatorBase
{
    protected GameObject gameObject;
    protected EmuGraphicsAdapter gfx;

    // Change those values and...go. ;)
    protected int disp_width = 20;
    protected int disp_height = 20;

    public int getDisplayWidth() { return disp_width; }
    public int getDisplayHeight() { return disp_height; }

    public EmulatorBase(GameObject g)
    {
        Debug.Log("EBASE Constructor");
        gameObject = g;
        gfx = g.GetComponent();
    }

    // Update is called once per frame
    public virtual void Update() { }
}

  • gfx ist nochmal/wieder die Referenz auf die Graphik-Komponente, welche im vorigen Artikel erstellt wurde.
  • gameObject ist das gameObject welches vom EmuCreator übergeben wird, also das gameObject, in welchem der EmuCreator als Komponente registriert ist.
  • disp_width und disp_height ist die native Grösse des Displays (in Pixeln) des jeweiligen Systems. Stelle diese Werte im Konstruktor ein.
Jede von dieser Basisklasse abgeleitete Klasse kann als Emulator in den EmuCreator "gestöpselt" werden. Update wird "normal" aufgerufen, also in jedem Frame. Update ist virtuell und muss in abgeleiteten Klassen mit override überschrieben werden. Im Konstruktor deines Emulators musst du nur noch disp_width und disp_height richtig einstellen und schon geht alles.

Dieser Platzhalter-"Emulator" setzt einfach einen Pixel. Später folgt noch ein schöner Grafikeffekt. ;)

Placeholder_Emulator.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Placeholder_Emulator : EmulatorBase
{
    public Placeholder_Emulator(GameObject g) : base(g)
    {
        Debug.Log("EPlaceholder_Constructor");
        disp_height = 50;
        disp_width = 50;
    }

    public override void Update()
    {
        gfx.MarkPixelToChange(19, 19, Color.white);
    }
}

Somit können nun verschiedenste Systeme über dieselbe Schnittstelle mit Unity "kommunizieren".

Ich hoffe, das hilft bei deiner Gesundung. :)

Das komplette Projekt befindet sich in diesem Git-Repository, im JUMPEE-Verzeichnis.

Weiter gehts mit ein bisschen Auflockerung: Ein Grafik-Effekt (ohne Shader - wenn mir das jemand in Shadern machen könnte, also das Display, das wär was.)

Freitag, 11. Oktober 2019

DIY Emulator Teil 2: Setup Basisgerüst (UNITY)

DIY: Emulator Teil 2: Setup Basisgerüst

in Unity

Früher hatte ich erwähnt, dass ich den Emulator am liebsten mit Unity3D machen würde, jedoch RTT (Render-To-Texture) damals nicht möglich war mit der frei verfügbaren Edition.

Nun, jetzt gibt es eine Möglichkeit.

Nachdem ich dieses Tutorial gefunden hatte,
habe ich erst mal das neueste Unity installiert: 2018.4.11f
und das Tutorial ausgetestet.

(Hier gehts zum vorigen Artikel: Teil 1: Research (deutsch)
Dabei ging ich noch von Javascript aus, dazu hat es auch ein paar Artikel.
JS war jedoch nur eine Notlösung, siehe oben...)

Nun gut, das läuft..

...machen wir was draus.

Wie schon beschrieben bin ich ein grafischer Mensch also wird erst mal ein Basis-Grafikgerüst aufgesetzt.

Mit dem Setup unten wird am Ende ein Bild auf dem Bildschirm dargestellt, auf welchem der Emulator sein Display zeichnet. Das Bild hat die Pixelgrösse des Emulator-Systems und wird auf Fullscreen hochskaliert. Wenn das keepAspectRatio-Flag gesetzt ist, wird das Bildseitenverhältnis des Original-Displays beibehalten. Dabei gibt es eventuell schwarze Balken an den Seiten oder oben und unten.

Der Emulator kann mit setEmuScreenSize die Displaygrösse anpassen und mit MarkPixelToChange die Farbe eines Pixels ändern.

Das Bild wird gebuffert, das heisst, im Hintergrund wird das Bild gezeichnet während im Vordergrund der "alte" Buffer angezeigt wird.

Man kann mehr als zwei Buffer-Images bestimmen oder auch nur eines: Dann wird direkt auf die sichtbare Textur gezeichnet.

Los gehts...

Lösche alles bis auf die Main-Kamera.

* Erstelle ein leeres GameObjekt auf Position 0,0,0
* Erstelle ein neues Script und hänge es an das GameObjekt an.

Das ist der Code, er wird unten erklärt:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EmuGraphicsAdapter : MonoBehaviour
{
    // each display has the same sprite size.
    protected Vector2 sprite_size;
    protected Vector2 local_sprite_size;

    // The actual emulator display game object which is visible on the screen.
    protected GameObject m_display_image;
    protected SpriteRenderer m_display_renderer;

    // at least 2 sprites for the buffering process.
    public int Buffer_Size = 2;
    protected Sprite[] m_buffer_sprites;
    protected int m_actualBuffer = 0;

    // keep the emulator aspect ratio?
    public bool keepAspectRatio = false;

#if (UNITY_EDITOR)
    private bool m_oldKeepAspectRatio = false;
#endif

    protected Texture2D m_drawable_texture;
    protected Sprite m_drawable_sprite;
    Color32 Reset_Colour = new Color32(0,0,69,0xFF);
    Color32[] m_cur_colors;

    // Start is called before the first frame update
    void Awake()
    {
        if (Buffer_Size < 1)
            Buffer_Size = 1;

        Debug.Log("EmuGraphicsAdapter started.");

        // create the display image game object.
        m_display_image = new GameObject("DISPLAY");
        m_display_image.AddComponent<SpriteRenderer>();
        // get the renderer to not search for it in each frame.
        m_display_renderer = m_display_image.GetComponent<SpriteRenderer>();

        // set some settings on the renderer.
        m_display_renderer.flipY = true;
        m_display_renderer.receiveShadows = false;
        m_display_renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;

        setEmuScreenSize(50, 50);
    }

    // call this function after the screen has resized or the sprite has changed.
    protected void resized()
    {
        // resize.
        var sprr = m_display_image.GetComponent<SpriteRenderer>();

        // The real image size in pixels for later.
        sprite_size = sprr.sprite.rect.size;
        // and in local space units.
        local_sprite_size = sprite_size / sprr.sprite.pixelsPerUnit;

        // get the screen height & width in world space units
        float worldSpriteWidth = sprr.sprite.bounds.size.x;
        float worldSpriteHeight = sprr.sprite.bounds.size.y;

        float worldScreenHeight = Camera.main.orthographicSize * 2.0f;
        float worldScreenWidth = (worldScreenHeight / Screen.height) * Screen.width;

        // initialize new scale to the current scale
        Vector3 newScale = m_display_image.transform.localScale;

        // resize the local scale to the world scale.
        newScale.x = worldScreenWidth / worldSpriteWidth;
        newScale.y = worldScreenHeight / worldSpriteHeight;

        // maybe keep the aspect ratio
        if (keepAspectRatio)
        {
           // get the aspect ratio of the sprite image.
           float aspectRatio = worldSpriteWidth / worldSpriteHeight;
           Debug.Log("Aspect Ratio: " + aspectRatio);
           float newy = newScale.x/aspectRatio;
           float newx = newScale.x;
           float wrldy = newy * local_sprite_size.y;
           // new height is bigger than screen height, switch aspect ratio calculation.
           if (wrldy > worldScreenHeight)
           {
                newy = newScale.y;
                newx = newScale.y * aspectRatio;
           }
           newScale.x = newx;
           newScale.y = newy;
        }
        // set the new local scale on display gameobject.
        m_display_image.transform.localScale = newScale;

        Debug.Log("Emulator display size (Image Pixels): x" + sprite_size.x + " y" + sprite_size.y+" (P->LS) x"+local_sprite_size.x+" y"+local_sprite_size.y);
        Debug.Log("Emulator display size (LS): x" + (newScale.x*local_sprite_size.x)+" y"+(newScale.y*local_sprite_size.y));
        Debug.Log("Real display size (WS): x" + worldScreenWidth + " y" + worldScreenHeight);
    }

    // switch the buffer images.
    protected void switchBuffers()
    {
        // apply all pixel changes to the draw texture.
        ApplyMarkedPixelChanges();

        // set the old stuff to the displayed image.
        m_display_renderer.sprite = m_buffer_sprites[m_actualBuffer];

        // set next buffer image.
        m_actualBuffer++;
        if (m_actualBuffer >= Buffer_Size)
            m_actualBuffer = 0;

        // set the new drawables.
        m_drawable_sprite = m_buffer_sprites[m_actualBuffer];
        m_drawable_texture = m_drawable_sprite.texture;

        m_cur_colors = m_drawable_texture.GetPixels32();

        clearDrawArray();
    }

    // create new textures for a new display size.
    public void setEmuScreenSize(int width, int height)
    {
        m_buffer_sprites = new Sprite[Buffer_Size];

        // create the buffer sprites.
        for (int i = 0; i < Buffer_Size; i++)
        {
            Debug.Log("Creating buffer image #" + i);
            Texture2D tex = new Texture2D(width, height);
            tex.filterMode = FilterMode.Point;
            //tex.Apply(false);
            Sprite spr = Sprite.Create(tex, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f));
            m_buffer_sprites[i] = spr;
        }

        m_display_renderer.sprite = m_buffer_sprites[0];

        // resize the displays.
        resized();

        // initialize the buffers, set the draw image etc.
        switchBuffers();
    }

    // set or unset the keep aspect ratio flag by code.
    void setKeepAspectRatio(bool setflag)
    {
        keepAspectRatio = setflag;
        resized();
    }

    // Update is called once per frame
    void Update()
    {
#if (UNITY_EDITOR)
        // maybe the keep aspect ratio flag has changed.
        // this will only happen in editor when you click the flag box.
        // else, setKeepAspectRatio(bool setflag) should be used.
        if(keepAspectRatio!=m_oldKeepAspectRatio)
        {
            m_oldKeepAspectRatio = keepAspectRatio;
            resized();
        }
#endif
      // Pixel Test
      //  MarkPixelToChange(10, 10, Color.green);
      //  MarkPixelToChange(11, 11, Color.yellow);
      //  MarkPixelToChange(15, 15, Color.yellow);
 
       switchBuffers();        
    }

    // DRAW FUNCTIONS
    public void MarkPixelToChange(int x, int y, Color color)
    {
        // Need to transform x and y coordinates to flat coordinates of array
        int array_pos = y * (int)m_drawable_sprite.rect.width + x;

        // Check if this is a valid position
        if (array_pos > m_cur_colors.Length || array_pos < 0)
            return;

        m_cur_colors[array_pos] = color;
    }
 
    public void MarkPixelToChangeByIndex(int index, Color color)
    {
        if(index>=0 && index<m_cur_colors.Length)
             m_cur_colors[index]=color; 
    } 
 
    // apply all pixel changes.
    public void ApplyMarkedPixelChanges()
    {
        if (!m_drawable_texture)
            return;

        m_drawable_texture.SetPixels32(m_cur_colors);
        m_drawable_texture.Apply();
    }

    // clear the drawing image.
    public void clearDrawArray()
    {
        for(int i=0;i < m_cur_colors.Length; i++)
        {
            m_cur_colors[i]=Reset_Colour;
        }
    }
}

Die Member-Variablen: 

  • sprite_size : Die Grösse des Emulator-Displays in Pixeln.
  • local_sprite_size: Die Grösse des Emulator-Displays in Units.
  • m_display_image: Das GameObjekt, in welchem der sichtbare Content dargestellt wird.
  • m_display_renderer: Die "SpriteRenderer"-Komponente von m_display_image. Damit muss man nicht immer wieder GetComponent aufrufen.
  • Buffer_Size: Wie viele Bilder werden gebuffert? Doublebuffer = 2, Triplebuffer = 3, etc.
  • m_buffer_sprites: Array mit der Anzahl an Buffer-Sprites welche in Buffer_Size angegeben ist.
  • m_actualBuffer: Der Index des aktuellen Buffer-Sprites in m_buffer_sprites.
  • keepAspectRatio: Das Bild wird auf den gesamten Bildschirm skaliert, wenn dieses Flag auf False ist. Ansonsten wird das Bildverhältnis (x/y) beibehalten und es gibt eventuell schwarze Balken am Rand. Dafür werden die Pixel korrekt dargestellt.
  • m_drawable_texture: Die aktuelle Textur, auf welcher gezeichnet wird.
  • m_drawable_sprite: Das zugehörige Sprite zu m_drawable_texture.
  • Reset_Colour: Die Hintergrundfarbe des Displays.
  • m_cur_colors: Array, auf welchem die Farben geändert werden können. Wird nach jedem Frame neu erstellt für den nächsten Buffer. Dieses Array wird später mit texture.setPixels(...) und texture.Apply() auf die Textur geladen. Wenn man das für jede Pixel-Änderung einzeln machen würde, wäre es wohl sehr langsam.

Die Funktionen:

  • Awake(): Diese Funktion wird VOR Start() aufgerufen und dient der Initialisierung "dieses" Objekts. Hier wird ein neues GameObjekt namens DISPLAY erstellt, welches die sichtbare Grafik darstellt. Dazu wird eine "SpriteRenderer"-Komponente an das "DISPLAY" angehängt und einige Werte eingestellt. Dann wird mit setEmuScreenSize(w, h) das Display initialisiert.
  • resized(): Wenn die Bildschirm- oder Displaygrösse geändert wird, sollte diese Funktion aufgerufen werden. Sie skaliert das Display auf die richtige Grösse.
  • switchBuffers(): Ruft ApplyMarkedPixelChanges() auf, setzt dann den neuen Buffer zum drauf zeichnen und zeigt den vorherigen (fertig gezeichneten) Buffer an.
  • setEmuScreenSize(width, height): Setze die Grösse des Emulator-Bildschirms in Pixeln. Danach wird resized() und switchBuffers() einmal aufgerufen.
  • setKeepAspectRatio(bool): Wenn dieses Flag gesetzt ist, wird das originale Seitenverhältnis beibehalten, ansonsten wird alles (x und y) auf den Bildschirm hochskaliert.
  • Update() : Update wird pro Frame ein mal aufgerufen. Home of switchBuffers() :)
  • MarkPixelToChange(x,y,color): "SetPixel" mit anderem Namen weil ApplyMarkedPixelChanges() aufgerufen werden muss, damit die Änderungen erkannt werden.
  • MarkPixelToChangeByIndex(index, color): Da das Emulator-Display normalerweise gleich gross ist wie das anzuzeigende Bild, kann man hier direkt mit dem Index arbeiten. Das verschnellert den Code ein bisschen.
  • ApplyMarkedPixelChanges(): Überträgt das mit MarkPixelToChange() modifizierte Color-Array auf die aktuelle Buffer-Textur.
  • clearDrawArray(): Setzt alle Pixel des Displays auf die angegebene Hintergrundfarbe (Reset_Colour).
Die Variablen zwischen #if(UNITY_EDITOR) und #endif werden im Spiel nicht gebraucht: Damit wird gecheckt, ob der Kasten im Editor angeklickt wurde.

Als nächstes wird ein Framework für die Emulatoren selbst erstellt:
DIY Emulator Teil 2.1: Plugin-Emulator (UNITY)

Das komplette Projekt befindet sich in diesem Git-Repository, im JUMPEE-Verzeichnis.

Ich hoffe, das gefällt. :) 

Dienstag, 10. März 2015

Part 2A: The Graphics Engine

This is Part 2A of my tutorial series about how to write an emulator.

In this part we will set up some graphics where the emulator can "draw to".

After reading Part 2 you should have created a basic WinPhone & MonoGame project, which we will extend now.

Note: If you downloaded the source from Part 2, you need to move some static methods to the new created GFXEngine.cs file (which is also there, but empty).

The Graphics-Engine needs to draw a texture on the screen, which is scaled to the screen size or an appropriate format (4:3 etc). There needs to be a method which can put all the graphics data from the emulator (intended to be a 1-dimensional Int32 array in the size consoleScreenWidth*consoleScreenHeight) onto this texture.

The texture itself will be the size of the console screen, but scaled to the size of the device (phone) screen. If the emulator changes, it should create a new texture in the appropriate size and reset the scaling factor.

There's more to that: A console can have more pixels than there will be seen. These must be covered or be outside of the device screen.

After this tutorial, you have a texture to draw on, which is scaled to the right size and in portrait mode, rotated about 90 degrees.

It will be filled with test pixels - you don't need to do that, it's only that you see something.

A little note: I am talking about UNSIGNED integers here. When I write Int32, you can assume it's meant UInt32. Unsigned means that there can be only positive numbers, not negative ones. This leaves one bit more for the value itself. Otherwise that bit would be used to determine if it's a positive or negative number.

Implementing

Ok then, first remove all the SpriteBatch-related code in Game1.cs.
We will implement that into our graphics-engine.

Now, lets create a new class called GFXEngine.cs.

public class GFXEngine
{
     // the graphics device from the engine.
     private GraphicsDevice graphicsDevice;

     // a SpriteBatch to draw sprites/textures.
     SpriteBatch spriteBatch;

     // the texture to draw on.
     Texture2D texture;

     // the device screen resolution is stored here.
     public int DeviceScreenWidth { get; private set; }
     public int DeviceScreenHeight { get; private set; }

     // Get device screen resolution
     // You need to call that before the game starts/outside of the game thread.
     public void ActualizeScreenResolution() {}

     // use this as constructor, it's called in a "safe region".
     public void LoadContent(GraphicsDevice gfxDevice) {}

     // the update call from the game engine.
     public void Update(GameTime gameTime) {}

    //the draw call from the game engine.
    public void Draw(GameTime gameTime) {}
}


That class will be extended later. First, we implement it into the engine.

Create an instance of the class in the Static.cs - class.

public class Static
{
      public static GFXEngine GFX = new GFXEngine();
      ...
}

And now call the methods in Game1.cs.
Call Static.GFX.ActualizeScreenResolution(); in the Constructor of Game1.cs.
Call Static.GFX.LoadContent(GraphicsDevice); in the LoadContent-method of Game1.cs.
Call Static.GFX.Update(gameTime); in the Update-method of Game1.cs.
Call Static.GFX.Draw(gameTime); in the Draw-method of Game1.cs.

We have  implemented our GFXEngine into the game and will now extend the class to our needs.

Extending

The MonoGame-screen is in portrait-mode. That means, we need to rotate our texture 90 degrees.
If you managed to run it in "real" landscape mode, you can skip the rotation part and just use 0 at the right position.

Add an enum, the PI-Constant and the RGBAToInt32-methods to your Static.cs-file:

public static class Static
{
    ...
    public enum EColorShift
    {
        RED = 0,
        GREEN = 8,
        BLUE = 16,
        ALPHA = 24
    }

    public const double PI = 3.14159265359;
    ....
    public static UInt32 RGBAToInt32i(int R, int G, int B, int A = 255)
    {
        return RGBAToInt32b((byte)R, (byte)G, (byte)B, (byte)A);
    }

    public static UInt32 RGBAToInt32b(byte R, byte G, byte B, byte A = 255)
    {
        UInt32 c= (UInt32)(R << (int)EColorShift.RED);
        c += (UInt32)(G << (int)EColorShift.GREEN);
        c += (UInt32)(B << (int)EColorShift.BLUE);
        c += (UInt32)(A << (int)EColorShift.ALPHA);
        return c;
    }

    // additional method to get color values back.
    public static byte ColorFromInt32(UInt32 value, EColorShift color)
    {
        // value (bit-)shift right by color, then get last 8 bits (0xFF = 255 = 8 bits all up)
        // e.g: 0xAABBCC & 0xFF = 0xAABBCC & 0x0000FF = 0xCC

        return (byte)((value >> (int)color) & 0xFF);
    }
}

The enum defines the right values for bitshifting later.
If you have another format than RGBA (e.g. AGBR) then you have to change only these values.

We need PI to get the correct radian angle. (I tried with 0.5 and 1.0 but it did'nt work.)

The RGBAToInt32-methods are the main functions to create the color-values for the console screen array. (And the ColorFromInt32-method does the exact opposite - getting color values back from Int32)

It simply pushes all values (ranging from 0-255 / 0x00-0xFF) into an Int32 variable by bitshifting.

Bitshifting works like that: You have x which is 4 bits "wide", the last one is up (1): 0001 => x=1
Shift Left 2 (x = x << 2) means shift all bits two to the left: 0001=>0100 (<--) => x=4
Shift Right 1 (x = x >> 1) means shift all bits one to the right: 0100=>0010 (-->) => x=2

E.g: R=0x11, G=0x22, B=0x33 and A=0xFF will give this hex value into the Int32-variable: 0xFF332211 because A is shifted 24 bits to the left, then B shifted 16 bits to the left and G shifted 8 bits to the left. R remained "where it was".

This methods are used by the emulators and have nothing to do with the graphics engine internals.
(But they are set up to correspond to the texture data format used by the graphics engine.)
The graphics engine must react to this functions, not otherwise.

That's why this methods are in the Static-class and not in the GFXEngine class.

Now, lets extend the GFXEngine-class. First we need some variables and properties:

public class GFXEngine
{
...
      private Vector2 textureOrigin;
      private Vector2 deviceHalfScreenSize;
      private Rectangle consoleRectangle;
      private float scaleFactor = 1.0f;
      // and the rotation for the portrait-to-landscape-hack:
      private const float consoleScreenRotation = (float)(90 * Static.PI / 180);

      public DeviceScreenWidthForConsole { get { return DeviceScreenHeight; }}
      public DeviceScreenHeightForConsole { get { return DeviceScreenWidth; }}
...
}

We could compute the most of them in each frame, but that's not necessary and like that, it saves some performance.

textureOrigin is the position on the texture from where it is accessed from outside (rotated / positioned). It's half the texture size.
deviceHalfScreenSize is the half of the device screen size. No need to compute that in each frame.
consoleRectangle is the rectangle on the texture which will actually be drawn. That is for the cutoff lines.
scaleFactor is used to scale the texture to device screen size. There is no independent scale factor for x and y. It's the same for both.
consoleScreenRotation is the rotation of the texture. It needs to be rotated 90 degrees because it's in portrait mode. If you managed to use real landscape mode, just use 0 here.
DeviceScreenWidthForConsole and DeviceScreenHeightForConsole return the "real" width and height for the console screen. Because it's in portrait mode, this values return the "other" value instead of the "right" one. (Height = DeviceWidth and vice versa.)

ActualizeScreenResolution

Extend the ActualizeScreenResolution()-method:
public void ActualizeScreenResolution()
{
      DeviceScreenWidth = (int)Application.Current.Host.Content.ActualWidth;
      DeviceScreenHeight = (int)Application.Current.Host.Content.ActualHeight;
      deviceHalfScreenSize = new Vector2(DeviceScreenWidth * 0.5, DeviceScreenHeight * 0.5);
}

(It's better to multiplicate instead of dividing (* 0.5 instead of / 2))

This method gets the device screen width and height. If you call that Application.xxx stuff inside the game, there will be an asyncrounous error. The method is called once at startup.

LoadContent

This is somewhat our constructor. Stuff will be created and initialized here for the first time.
public void LoadContent(GraphicsDevice gfxDev)
{
     graphicsDevice = gfxDev;
     spriteBatch = new SpriteBatch(graphicsDevice);

     // create a first texture so there is one. (...and it's used for testing.)
     ResizeTexture(140,100); // for the exact same test image like mine, use size 34x24

     // build the test image.
     BuildTestImage();
}

The methods ResizeTexture and BuildTestImage will follow later. You don't need BuildTestImage, it's only here to see something because otherwise, the texture would be transparent.

Draw

Lets extend the Draw-method.
public void Draw(GameTime gameTime)
{
     // we only need the samplerstate here.
     // needed for pixel-perfect scaling without blur and stuff.
      spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend,
                                    SamplerState.PointClamp,
                                    DephtStencilState.Default,
                                    RasterizerState.CullCounterClockwise);

     // now draw the texture with all given values.
     spriteBatch.Draw(texture,
                           deviceHalfScreenSize,
                           consoleRectangle,
                           Color.White,
                           consoleScreenRotation,
                           textureOrigin,
                           scaleFactor,       // bad that there is no scaling for x / y individually.
                           SpriteEffects.None,
                           0.0f);

     spriteBatch.End();
}

We need to modify the SamplerState in SpriteBatch.Begin so there is no blurry image. All other values are left default. Then we give all the values which are computed in ResizeTexture to SpriteBatch.Draw. SpriteBatch.End must be called to end the draw call.

ResizeTexture

Create the method ResizeTexture(int,int,int,int):
public void ResizeTexture(int width, int height, int cutOffTopLines = 0, int cutOffBottomLines = 0)
{
        // first, create the new texture with given width and height.
        texture = new Texture2D(graphicsDevice, width, height);

        // hcalc is the height minus the cutoff.
        // it's stored in consoleRectangle and used for scaling and origin.
        float hcalc = height - cutOffTopLines - cutOffBottomLines;
        // origin needs to be calculated using hcalc instead of height.
        textureOrigin = new Vector2(width * 0.5f, hcalc * 0.5f);
        // define the shown rectangle on the texture.
        consoleRectangle = new Rectangle(0, cutOffTopLines, width, (int)hcalc);
        // now calculate the scale factor.
        scaleFactor = (float)DeviceScreenHeightForConsole / hcalc;
        // maybe it's a widescreen. (not possible, but why not calculate it?)
        if(width * scaleFactor > DeviceScreenWidthForConsole)
               scaleFactor = (float)DeviceScreenWidthForConsole / width;
}

Everything is explained in the comments above.

This is the basic "graphics engine". All we need to do now, are some methods to put the given array from the console to the texture. For that, we set up some helper functions to modify textures.

"Interface" Methods

I call those "Interface Methods" because this are the methods which the emulators need to interface with the graphics engine.

Create these four methods in GFXEngine.cs:
public class GFXEngine
{
    // set the data for the main (onscreen) texture.
    public void SetMainTexData<T>(T[] data) where T:struct
    {
        ArrayToTexture(data, texture);
    }

    // get the data from the main (onscreen) texture.
    public T[] GetMainTexData<T>() where T:struct
    {
        return TextureToArray<T>(texture);
    }

    // retrieves all colors from a texture in an 1-dimensional {T} array.
    public static T TextureToArray<T>(Texture2D tex) where T:struct
    {
        T[] colors1D = new T[tex.Width * tex.Height];
        tex.GetData(colors1D);
        return colors1D;
    }

    // set an 1-dimensional {T} array to a texture.
    public static void ArrayToTexture<T>(T [] cols, Texture2D tex) where T:struct
    {
         tex.SetData(cols);
    }
}

The T stuff means simply that you can get/put any data type you wish, as long as it's accepted by the texture code itself. You can either use an Xna.Color-Array or an Int32-Array or something else there. ...where T:struct defines the T-variables as not-nullable.

To create an array with the right size, just use the TextureToArray-method.

Int32[] ar = TextureToArray<Int32>(myTex);



Testimage

Last but not least, the test image:

public void BuildTestImage()
{
   Microsoft.Xna.Framework.Color[] c = GetMainTexData<Microsoft.Xna.Framework.Color>();

  // go through all the pixels and color them.
  for(int i=0; i<c.Length; i++)
  {
       // color every second pixel different.
       if(i % 2 == 1)
             c[i] = Microsoft.Xna.Framework.Color.Red;
       else
             c[i] = Microsoft.Xna.Framework.Color.Green;

      // color the first four lines.
      if(i < texture.Width * 4)
         c[i] = Microsoft.Xna.Framework.Color.Yellow;
      // color the first 2 lines.
      if(i < texture.Width * 2)
         c[i] = Microsoft.Xna.Framework.Color.Magenta;

      // color the last two lines
      if(i > (texture.Height - 2) * texture.Width)
         c[i] = Microsoft.Xna.Framework.Color.Magenta;
  }

    // set the new data to the texture.
    SetMainTexData(c);
}

You will see an image like this:


On the left and right there is the background which is actually in the color "Cornflowerblue".
If you have a widescreen image (e.g. 200x100px), the background will be visible on the top and bottom.

The first two lines (magenta, top and bottom) are used to test the cutoff. You can use ResizeTexture(w, h, 2,2) to test that. The yellow line indicates the first two lines of the "real visible" screen. Then, every pixel is either red or green. In the bottom of the image, you should see a green or red pixel in the magenta lines. That one indicates if the image is drawn from left to right.


Download

Download the Source for this Part:
Emu_02_GraphicsEngine.zip from Mirror 1 (OneDrive)
Emu_02_GraphicsEngine.zip from Mirror 2 (homeserver)


That's it with this part. You should now have a "graphics engine" which your emulators can use.

In Part 2B [TBA] we will create the "Sound Engine"/Sound Interface for the emulators to use.

Part 2B: The Sound Engine [TBA]

You can skip the whole Part 2x stuff and just go to Part 3 if you do that on another Engine/Platform.

Montag, 9. März 2015

Part 2: Basic Winphone Setup

This is part 2 in my tutorial series about writing an emulator.
I am doing this with WinPhone 8.1 and MonoGame.

If you don't know what I'm talking about, here is Part 1.

In this section, we will install the right development environment and create a basic game-app.

I take this part first because you and I want to see at least something, not? You can skip that part if you use another engine/platform.

OK, I will use MonoGame, the (open source) successor of XNA.
If you want to use DirectX, you have to use C++ and that's none of my business anymore.

At least now, you should have downloaded and installed MS Visual Studio 2013 Express Update 4 or something similar.

After that, you should install MonoGame.

You can download MonoGame from here:
http://www.monogame.net/downloads/
Use at least V. 3.2 which has templates for MS VS 2013.

With the install also comes OpenAL, which I decided to install because, as mentioned, I don't know nothing about sound programming and if there is an "Open Source Audio Library", why not use that?

Start up Visual Studio and create a new Application using the C#->"MonoGame Windows Phone 8 Project"-Template.

There you have three "main" files:
GamePage.xaml, which is your WinPhone-Layout-file for that page.
GamePage.xaml.cs, which is the source-file corresponding to the layout (file) - where your button-clicks and other UI-stuff will be handled.
Game1.cs, which is the game itself. It runs in it's own thread - you cannot simply use WP-UI-button-clicks and stuff here because that will maybe throw an Asyncronous-Exception. More about that later, when I know how to handle that myself.

The project should compile without errors.

If you get a blank screen, don't worry - that's a common issue with the MonoGame v3.2 template.
Follow this discussion to solve it:
http://community.monogame.net/t/3-2-on-windows-phone-8-just-shows-black-screen/278/12

I just uncommented the alternate grid on the bottom of GamePage.xaml and commented out the main one. Even if it should run now in portrait mode, it seems to be in landscape mode, though.

Some Static Functions

There are some methods which will be used through the whole app. Create a new class called Static.cs and put this methods in it:

using System.Windows;
using System.Windows.Controls;
...

// shows a standard text message.
public static void ShowMsg(string txt)
{
     MessageBox.Show(txt);
}

// go back to the last page or to the main page.
public static void GoBackOrMain(Frame frm)
{
     if(frm.CanGoBack)
          frm.GoBack();
    else
         frm.Navigate(new Uri("/GamePage.xaml", UriKind.Relative);
}


The first method, ShowMsg, is just here to get some info on the screen. You can use that for infos like "Loading failed, please try another ROM" or such stuff which only needs an OK-button.

The second method GoBackOrMain, tries to go back to the last opened page. If it cannot go back, it tries to open the main page. This method is for use with the back button and/or cancel buttons.

In WP 8.1 that works a little bit other but on WP 8.0 that's all you have to do.
That code is not tested yet (navigating).

The Back Button

The back button does not work like in other apps. You cannot use HardwareButtons.BackbuttonPressed += myFunc (WP 8.1?).

Open GamePage.xaml. Click on the events button in the properties window. Search for the event BackbuttonPressed or OnBackbuttonPressed and double click it.

You have now a method for your back button, use it like you want. I want to open a CommandBar with it, but first you should just exit the app with _game.Exit();

Other Stuff

There are other problems which I could not solve until now. I will post it here when I find a solution.
+ Cannot switch tasks, the game just shows the cleared screen after resuming.
+ Cannot even press the switch-task-button and then resume game directly.

Download

Download the Source for this Part:
Emu_01_WP_Setup.zip from Mirror 1 (OneDrive)
Emu_01_WP_Setup.zip from Mirror 2 (homeserver) (direct download)

It has some more static methods and draws a texture to the screen for testing.



In Part 2A we will set up the graphics for our emulators to have a "render device".

Part 2A: The Graphics Engine

You can skip the whole Part 2x stuff and just go to Part 3 if you do that on another Platform/Engine.

Donnerstag, 26. Februar 2015

How to write an emulator. (Part 1)

05.2015: I stopped working on this but I leave it online. Maybe I will continue that later.
05.2017: I started again working on this but I will do it with JavaScript now. This post totally needs an update for that approach but I will leave it as is and make a new post ( in german). So you can refer to this one for...stuff you may need.


I don't know. But I will write down, step by step, all what I do to accomplish it. (For my brains, can't hold all that. ;) )

This one is not for beginners in programming. Also, I will only cover how to make an old game console emulator. It's not about emulating a certain CPU - the CPU emulator will be included from third parties.

I am making a tutorial series about that. In this post we do some research. Actual work will be done in the next posts.

A friend of mine once wrote a Sega Master System Emulator. I did a graphical refresh for it.



Here is the Download (Windows exe and dll):
SMSEmu.zip on Mirror 1 (OneDrive)
SMSEmu.zip on Mirror 2 (homeserver) (direct download, maybe with left click/Save as..)

Since I lost the all the code and since I found out that WinPhone is an awesome platform to program with, I decided to do my own emulator and maybe a whole emulator pack. It could be fun.

First I want to re-do the SMS (Sega Master System), mostly because my friend has done it and...because I've got some ROMs.

Here are the SMS ROMs. Please ONLY use them for testing your emulator. DO NOT SHIP THEM with it. That would be illegal. I uploaded them only because they are relatively hard to find and you will need some ROMs at a certain point for further developing.

So, here's the ROM download:
SMSroms.zip on Mirror 1 (OneDrive)
SMSroms.zip on Mirror 2 (homeserver) (direct download)

Get The Basics

This is where I started. If you are new to the topic, have a look at it.

http://fms.komkon.org/EMUL8/HOWTO.html

But the links all are a little bit outdated. This tutorial came out when the internet just started, and all the newsgroups and stuff are mostly not actual and hip. ;)

Ok then, lets write that all down.
I want to do it with C#.
I HAVE to do it with C# if I want to port that to WinPhone.
(Actually, for using DirectX I would have to use C++. I hope, I find another solution.)

To develop WinPhone-Apps, you need something like this:
http://www.microsoft.com/de-ch/download/details.aspx?id=44914
(That's MS Visual Studio Express 2013 with Update 4 - WinPhone SDK should be included here.)

But there is a better version, Visual Studio Community. I don't know if that works with WinPhone, so I use the one recommended by Microsoft.

Some Theoretical Thoughts

I want to do a multi-purpose multi-platform emulator. It should be possible to emulate several consoles with one program and it should be possible to port the code to allmost any platform.

Best case scenario would be that you load any ROM and the application decides by filename and/or file signature, what emulator to create and start. You could put any supported ROM into your ROM folder and have to decide absolutely nothing (on the mobile phone, PC version needs an input configuration ;) ) and could just play the game - no matter if it's a Sega or Nintendo, MAME game and what console it actual is.

My platform decision is WindowsPhone, but I want to write the code as flexible as possible to include it in Unity3D or other Engines/Platforms. (With Unity3D, almost any platform can be reached, from PC/Mac/Linux over Android/iPhone/WP? up to *real* consoles like PS3/PS4/XBoxXXX/Wii. So, doing the emulator with Unity would be a great way to reach almost every people.)

Problem with Unity is that RTT (Render-To-Texture) is only possible with a paid version of Unity. You could not do RTT just for fun here. And that's what we need to render the old 2D-images on to a scaled texture which fits the whole screen.

[Edit] I am trying it with Unity, though. Maybe there is a possibility to, in any way, draw on the texture.

[Edit 2]: As of 2019, it is now possible to use RTT in Unity Free. I managed to do it with another project. It's not even hard to do. But I don't know exactly what I did. Just to note...RTT works with Unity Free now. So, Go for it.

But for the emulator, this is not a problem at all. The emulator just returns an array of pixels, which the graphics engine must use "outside" of the emulator. You can use just any graphics engine which can render pixels to a texture or directly to screen. If you want, you can try it with the Windows-Desktop-GUI-API. I will do it with the WinPhone API using Direct3D [MonoGame] and also try it with Unity.

Sound seems to be the most easy thing to emulate first from an old console [the SEGA Master System]. According to the tutorial ^ there was an SMS-emulator written in one Day - the only thing it could do was playing the sound from the ROM. So, I think, emulating the sound engine is more easy than the graphics itself.

Get The Facts


If you want to emulate something, you need to know about it.
I want to do an SMS emulator first. Friend gave me this links for it and mentioned the first (top) document, which he also used.

http://www.smspower.org/Development/Documents

This document will give more explanation than the other ones:
http://www.smspower.org/uploads/Development/richard.txt

I need to learn all I can about it...

Get The CPU

Most old - really old - consoles use the Z80-CPU as main CPU. That's the case for GameBoys, SEGA Master System, SEGA GameGear and I don't know what else. So, we will firstly use the Z80 CPU and maybe add some other CPUs later.

I did a search for a C# Z80 CPU Emulator and found this one:

https://code.google.com/p/cs80/source/checkout

Thank gods, it exists. We do not have to do it ourselves. Contributions go to: [Unknown]
It's released with the New BSD License.

I downloaded it using SlikSvn: https://sliksvn.com/download/

Friend told me we need only the Z80.cs file, all other stuff is (PC-?)hardware related addendum.



*(See below) I switched to Linux and try to do it now with Linux stuff. Please be patient.

More in the next parts,
[Not Done*]Basic Unity3D Setup
or
Basic Winphone Setup (with MonoGame).

You can skip that part if you want to use another Framework/Engine/Platform than WinPhone and Monogame. What you have to accomplish is to render pixels on the screen. I want to render them on an image (which could be stretched) and then show the whole image on the screen.

After that, we will Build Your Framework which is the interface between the engine and the emulators itself.