Seite 10 von 11

Re: Eigenes Videoformat

Verfasst: Fr 5. Jun 2020, 21:35
von DOSferatu
zatzen hat geschrieben:Hallo!

Vielen Dank für die Demo!
Ja, und ich hoffe, Dir ist der Fehler schon aufgefallen.
In den beiden "Block-Kopier" Routinen habe ich einfach diese 8 Zeilen im Code untereinander kopiert. Die add SI,AX und add DI,AX in der jeweils letzten der 8-Kopierzeilen sind selbstverständlich sinnlos und gehören weg!
zatzen hat geschrieben:Ich habe Spaß damit und freue mich sehr, meine Idee mal umgesetzt zu sehen.
Sieht lustig aus, ein Hokuspokus aus Rechtecken. Die Mosaik-Blende ist auch eine gute Idee, evtl. sogar für ein Spiel, um zu vermeiden, für einen kurzen Moment einen heftigen Datendurchsatz zu haben, was zu einem kleinen Hänger führen könnte.
Die habe ich nur so eingebaut, damit man mal die "Klotzkopier-Routinen" in Aktion sieht, weil sie ja sowieso da sind. Im eigentlichen "Spiel" fallen die ja nicht so einzeln auf (was sie ja auch nicht sollen).
zatzen hat geschrieben:Und von dem Code kann ich auch noch was lernen. Die Funktion PRED war mir z.B. neu, bedeutet in dem Zusammenhang im Code aber nichts anderes als -1 oder?
Ja, PRED(X) liefert den Vorgänger von X und SUCC(X) den Nachfolger.
Außerdem: Immer INC und DEC bevorzugen gegenüber + und -. Ich habe mal gelesen, daß der TP-Compiler damit intelligenteren Code erzeugt.
Also nicht so "BASIC-mäßig" A:=A+1; schreiben (in Basic wäre es A=A+1), sondern INC(A). Und statt A:=A+5; immer besser INC(A,5);
Wenn es dieselbe Variable ist, die das Ergebnis und der Parameter ist, macht's keinen Sinn, die doppelt zu nennen. Hab mir noch nicht angesehen, ob der TP-Compiler da selbst optimiert. Im blödesten Fall macht benutzt zweimal die Speicherreferenz der Variable. Bei A:=B+C; isses ja was anderes.
Das gleiche gilt für Subtraktion und DEC.
Und wenn man den Vorgänger/Nachfolger einer Variable/Wert in einer Formel (also einem "Ausdruck") benutzen will, kann man PRED und SUCC nutzen.
Plus und Minus benutz ich quasi fast nur noch wenn ich WIRKLICH mal (was selten genug vorkommt) mit Fließkomma-Blödsinn arbeiten muß. Bei Integer/Festkomma immer nur INC/DEC usw.
zatzen hat geschrieben:Aber auch überhaupt, wie Du die Sache technisch angehst wird mir helfen und mich inspirieren.
Das war der Plan dahinter, deshalb der Source. Ich dachte so: Ich kann es in elend langen Texten erklären oder ich mache einfach mal ein Programm, das das macht. Dann kann man daran rumspielen und das selbst nachvollziehen.

Auf die VGA werden keine Sprites geschrieben, die kriegt damit immer nur 8x8-Blocks, die dann Teile von Sprites enthalten - bzw. auch Teile, wo im letzten Zyklus ein Sprite war (um das zu löschen). Deshalb auch die 2 Phasen. In jeder Phase wird immer das "Neue" hinzugefügt. Das "Neue und Alte" kopiert, aber für's nächste Mal nur das "Neue" behalten (das dann beim nächsten Mal das "Alte" ist).
zatzen hat geschrieben:Ich hab das hier in Dosbox ersteinmal mit den default nur 3000 Cycles laufen gehabt am Laptop, das entspricht etwa 286er Niveau. Bis ca. 8 Sprites hab ich volle 70 fps (je nach zufälliger Größe), dann bis ca. 25 Stück noch 35, und ab etwa 50 dann 23,3. Dabei kratzen die Anzahl der Blöcke aber schon an der 400er Marke. Für "286er" aber eine ziemlich gute Performance. Bei 20000 Cycles (eher langsamem 486 entsprechend) hab ich bis etwa 128 Sprites 70 fps, und dann sind es schon 700 Blöcke. Je mehr Sprites man hat, desto langsamer steigt die Anzahl der Blöcke (klar, wegen Überlappung), das ist gut.
Ja, man sollte bedenken, daß bei einem starren (nicht scrollenden) 320x200 Screen mehr als 20 "mittelgroße" (20x20 bis 30x20) Sprites kaum noch Sinn machen für ein Spiel (weil nur noch unübersichtliches Gewusel). Eher ein paar große Sprites (Figuren, "Gegner"), ein paar mehr kleine ("Schüsse"), ein paar wenige "Boni", das war's.

Außerdem: WICHTIG! Wenn man fast das ganze Feld mit Sprites bedeckt und so ständig schon mehr als ca. 30% des Feldes mit Sprites bedeckt, gewinnt man performance-mäßig gegenüber Flipscreen oder Komplettneuzeichnen GARNIX - sondern verliert eher noch durch die etwas kompliziertere Klotzgeschichte - und verbraucht Speicher, ohne Performance zu gewinnen.
zatzen hat geschrieben:Sicherlich wird es so sein, dass Pixeldaten-Sprites noch einmal mehr Performance kosten als gezeichnete leere Rechtecke.
Eine sehr wichtige Aussage, auf die ich Dich gleich nochmal hinweisen werde!
zatzen hat geschrieben:Wenn man wirklich ein Spiel mit festem Screen ohne Scrolling machen will ("Gobliiins" ist z.B. so eins) macht dieses Verfahren in dem Fall sicherlich mehr Sinn als jedesmal den ganzen Hintergrund zu restaurieren, und Double-Buffering mit Mode-X ist dann nicht unbedingt notwendig - auch wenn man dabei Speicher sparen würde.
Tja, es ist, wie schon öfter erwähnt, immer ein Trade-Off zwischen Performancegewinn und Speicherverbrauch.
zatzen hat geschrieben:Aber man kann ja probieren das Hintergrundbild zu komprimieren,
Und DAS war dann wieder die Stelle im Text, wo sich mir die Nackenhaare aufstellten...
NEIN!
Man gewinnt mit etwas aufwendigen Dingen und 129000 Bytes Speicherbedarf (plus Speicher für zusätzliche Routinen) eine einigermaßen gescheite Performance für ein paar wenige Sprites, dabei noch zu berücksichtigen (wie oben erwähnt), daß echte Sprites natürlich mehr Performance kosten werden als diese einfach Platzhalter-Rahmen, die ich da zur Veranschaulichung drin hab...
Und gleich fällt wieder das blöde "Komprimieren"-Wort - um wieder alles zunichte zu machen!
Es ist noch nicht einmal klar, wie es am Ende performen wird, denn in die "Spiel-Schleife" gehören noch:
* Eine RICHTIGE Sprite-Routine
* Eine RICHTIGE Figuren-Steuerung
* Vernünftiger Steuereingabe/Abfrage-/Verarbeitung der Spieler-Eingaben
* Evtl. Ausgabe von Werten in einer "Anzeige" (entweder außerhalb Spielfeld oder "drüber")
* Selbstverständlich Routinen, die Soundeffekte/Musik berechnen und Abspielen
* einiges an Kleinkram, damit der ganze Schlunz als Einheit zusammenarbeitet...

--- Aber, schon BEVOR getestet ist, ob das alles zusammen dann auch noch einigermaßen läuft, geht es gleich wieder an's "bauen wir eine speicher-/performancefressende intelligente Kompression ein"...

Nur so zur Erinnerung: Das ganze Ding, so wie es jetzt ist, arbeitet mit unkomprimiertem Zeug und quasi "unrolled" Kopier-Routinen für die Blocks und schafft damit gerade mal so "auf Kante" eine einigermaßen gescheite Performance, wenn wenige/kleine Sprites benutzt werden - und ohne dabei ein Spiel zu sein, sondern nur eine kleine Machbarkeits-Demo.

Will damit sagen: Wenn die komprimierten Daten PLUS Kompressionsroutine und benötigte Tabellen etc. INSGESAMT weniger als 64000 brauchen UND kaum mehr Performance kosten, dann wäre sie VIELLEICHT sinnvoll - glauben kann ich's kaum. Die 64k-"TEMP" Seite kann man eh nicht komprimieren, nur das "Ausgangsbild". Und da müßte man dann immer wenn man einen Klotz holt, eben dekomprimieren.

Ich sag's mal so: Ich habe - zu einfachen Demo-Zwecken - hier die "GetMem"-Methode benutzt. In Wirklichkeit würde jemand, der nicht doof ist, selbstredend Segmente reservieren, die bei $0000 anfangen. Dann wäre die Klotzkopiererei sogar noch ein klein wenig schlauer zu machen, weil man sich den blöden eventuell vorhandenen Offset spart.

(Ja ich weiß: Man kann GetMem 64016 machen und dann bei 0 anfangen, indem man immer das Segment+1 benutzt. Dann hätte man nur in Hochsprache nicht so einfach die Rechtecke machen können mit dem Array... - ich wollte es einfach halten. Da geht noch etwas Optimierung.)
zatzen hat geschrieben:Manche Bereiche in dem Brückenbild von MI2 sehen jedenfalls so aus, als hätten sie nur 2 oder 4 Farben (z.B. der Himmel).
Dieses Bild ist keinerlei Referenz für ein Spiel und sollte nicht als Beispiel für irgendwelche Auswertungen/Einschätzungen herangezogen werden. Es war ein zufällig vorhandenes 320x200-Bild, das ich benutzt habe. Kompression in solchen Dingen hat den Nachteil, daß man nie vorher weiß, wieviel Speicher es wirklich belegt. Wenn man Pech hat, erzeugt das gewählte Bild in Kombination mit der gewählten Kompressionsroutine einen Worst-Case und ist am Ende sogar größer als ungepackt. (Wenn man nix außer RLE kann, kann einem das z.B. leicht passieren, wenn man gleichzeitig bei Grafik ein Fan übermäßigen Ditherns ist.)
zatzen hat geschrieben:Ein tatsächliches Level-Bild könnte dann eine Mischung aus beliebigen und kachelartigen Formen sein, teils ähnlich wie bei Lemmings (bloß da hat man keinen Hintergrund in dem Sinne), wo je nachdem eine wie o.g. Komprimierung sehr gut anschlagen könnte. Man könnte auch bewusst Kachelweise arbeiten im 8x8 Raster, dann würde besonders die Redundanzkomprimierung greifen.
Ja... ich will mich ja nicht ungefragt einmischen und so und ich weiß auch, daß Du unbedingt immer gern mit Deinen Routinen herumspielen willst. Es geht eben darum, ob es ein spielbares Spiel werden soll oder eine Tech-Demo mit möglichst komplexen Routinen. Ein Spieler dankt einem diese Mühe nicht, wenn es am Ende nur ein speicherfressendes ruckeliges Ding ist. Dem ist total egal, wie schick und komplex die Kompression ist. Daher muß man sich da immer die Frage stellen, was das Ziel ist.

Ich selbst würde an so einem frühen Punkt der Spielentwicklung wohl nicht in solche Details abgleiten, wenn die die Dinge, die man eigentlich braucht, noch gar nicht da - und nicht mal angedacht sind.

Will damit sagen: Anfangen nach krasseren Kompressionsverfahren würde ich wohl erst, wenn ich merken würde, daß mir der Speicher für die Sachen ausgeht. Also ja: Ich bin immer für die Kachel-Idee, weil die Speicher spart ohne den Aufwand großartig zu erhöhen: Also: Wenn im "Bild" gleiche Kacheln sind, diese zusammenzufassen und nur "Nummern" zu benutzen. Dann können die Kacheln weiterhin mit einer einfachen 32Bit * 16 Kopier-Routine kopiert werden. Wenn man INNERHALB der Kacheln dann aber wieder "packt", dann beraubt man sich diesen Vorteils: Wenn die Kacheln dann wieder einzeln irgendwie "entpackt" werden müssen, gewinnt man nichts mehr an Performance.

Anmerkung dazu - betreffend DOSBox: Das ist es, wieso DOSBox nicht so 100% gut als Performance-Tester geeignet ist: Die 3000 Zyklen für "286er" Einstellung sind zwar gut als Mittelwert-Einschätzung, aber die emulieren das dann einfach wie einen Rechner mit 486er-Befehlen der auf 286er runtergebremst ist. Ein echter 286er kennt ja z.B. keine 32-Bit-Befehle (der würde aussteigen, sobald ihm jemand diesen $66-Präfix vor die Füße wirft!), was bedeutet: Die Block-Kopier-Routine wäre auf echtem 286er nicht nur soviel langsamer wie 3000 zu 20000 ausmacht, sondern sogar noch mal um ca. die Hälfte langsamer, weil doppelt soviele (16bit- statt 32-bit) Kopier-Befehle nötig wären und diese "Stringcopy"-Befehle auch auf 286er nicht gerade optimal umgesetzt sind und wesentlich mehr Zyklen brauchen.

DOSBox ist gut, um ein vorhandenes Stück Software so gut wie möglich abzuspielen, um "alte" DOS-Sachen abzuspielen usw. Aber. da es kein "1:1" System-Emulator ist, ist es nur begrenzt geeignet, eventuelle Performance auf einem realen System damit abzuschätzen.
zatzen hat geschrieben:Für Vordergrundebenen könnte man weitere "Hintergründe" mit Transparenz verwenden, und die Bereiche die diese abdecken wiederum im wirklichen Hintergrund transparent halten, so dass dort Daten und Transfer gespart werden - insgesamt hätte man so trotz mehrerer Ebenen im Prinzip nicht mehr Bilddaten und Datendurchsatz als hätte man nur einen Hintergrund ohne Vordergrundebenen.
Ja, wie gesagt: Natürlich alles schöne Ideen - und auch wert, darüber nachzudenken. Allerdings vielleicht eine gute Idee, erst einmal ein spielbares kleines Teil zu bauen, um dann zu sehen, ob diese zusätzlichen Features dann noch möglich sind. Wieso sag ich das? Weil es schade wäre, all das schon vorher einzubauen und sobald dann eine Steuerung und Soundroutine und all das, was man sonst so braucht, eingebaut sind, man dann erst merkt, daß die Performance weg ist, das Spiel nicht mehr funktioniert/spielbar ist und man sich dann die schöne Mühe umsonst gemacht hat.
zatzen hat geschrieben:Ich glaube ich werde da erstmal einen Converter schreiben, der komprimierte Hintergrund-/Vordergrund-Daten erzeugt, sowas macht mir ja Spaß.
Ich weiß - das ist ja das Schlimme.
zatzen hat geschrieben:Mir geht es ja bei einem Spiel nicht nur um das sichtbare Endergebnis, sondern auch in einem großen Maß um die ungewöhnlichen Techniken die ich einbaue, die auf meinem Mist gewachsen sind. Spielerei eben auch, Dinge realisieren die mir lange Zeit verwehrt blieben und dank Assembler jetzt mit brauchbarer Performance machbar sind.
Ja, aber Assembler ist auch keine Magie. (Auch wenn es sich, wenn man von Hochsprache drauf umsteigt, zuerst mal so anfühlt, wenn man den Geschwindigkeitsunterschied sieht.) Allerdings kann ich, der schon eine Zeitlang auch schon viel Zeug mit 100% ASM baut, dazu sagen: Auch ASM hat Grenzen und man kriegt es auch an irgendeinem Punkt irgendwann groß+langsam. Und sich erst die Mühe für ASM zu machen, um dann am Ende kaum wirklich etwas damit rauszureißen... - aber naja, ich wiederhole mich.

Ja, entschuldige meine harten Worte. Ich werde da in letzter Zeit immer pragmatischer. Ich find's auch gut, wenn mal irgendwas funktioniert. Und wenn irgendwas funktioniert, weil oder OBWOHL die Idee dahinter recht simpel ist, schäme ich mich nicht deswegen, sondern freue ich mich, daß es funktioniert. Komplizierte Dinge können helfen, wenn der Erfolg sie rechtfertigt - aber eben auch nur dann. Ansonsten sind sie eben ... naja, nette Experimente ohne praktischen Nutzen. Da es ein Hobby ist, muß es ja keinen praktischen Nutzen haben, das weiß ich. Aber damit entfernt man sich eben andererseits auch immer mehr vom "Endziel Spiel".

Ich denke da immer an dieses "KISS-Prinzip", das dieser Typ da mal formuliert hat: "Keep It Simple and Stupid". So blöd es klingt, aber, damit hat man manchmal mehr Erfolg als mit vielem anderen.

Da gibt es diese Episode von so ehemaligen Microsoft-Mitarbeitern (und auch bei anderen Softwarefirmen) : So "Chefs" und "Manager" sind ja fachlich oft blöde und verstehen nicht wirklich, was ihre Angestellten da eigentlich machen. Und dann kann es passieren, daß Angestellte besser bewertet wurden (weil "fleißiger"), weil sie mehr (also größeren) Code geschrieben haben, weil das ja "mehr ist". - Aber ein wirklich guter Programmierer schreibt kleinen schnellen schlauen Code und nicht einfach nur "viel" - sowas begreifen so "Mengen"-orientierte "mehr = besser" Typen nicht.

Z.B.: Was ist daran cool, daß Windows inzwischen nicht nur MB- sondern GB-Größe hat? Ist das n Grund, stolz drauf zu sein? - Eigentlich doch eher nicht: Wird immer größer und langsamer (obwohl die Rechner immer schneller werden) - wo ist da die programmiererische Leistung?

Und kleine schnelle Dinge können oft mehr als große langsame. Das will nur keiner mehr hören heutzutage, weil immer nur "größer=besser"...
zatzen hat geschrieben:
DOSferatu hat geschrieben:Und ja, ich hab mir das Spielchen mal angesehen... Naja, ehrlich gesagt: So'n Ding zu bauen wäre für mich inzwischen keine Herausforderung mehr. Mir wäre es etwas schade um die Zeit, die ich damit verbringe, sowas zu bauen, wenn ich eigentlich auch Zeug hier hab, um was Cooleres zu machen.
Klar, ich meinte nur, damals, ca. 1991. Als ich noch so wenig Verständnis von den ganzen Sachen hatte, gerade mal Text auszugeben vermochte in Basic, und über das Buch "Spiele Programmieren mit QBasic" Plötzlich eine Möglichkeit reinkam, einfache Pixelgrafik darzustellen mit PUT, das war für mich schon revolutionär. Und etwa auf diesem Niveau ist dieses Alf-Spiel - ich wollte hier im Grunde auch nur sagen dass ich damals meine Ideen mit so rudimentärem Programmierwissen einfach umgesetzt habe.
Naja, ich glaube, wir ALLE, die wir jemals programmiert haben, haben damals damit angefangen, bekannte Sachen zu versuchen, irgendwie nachzumachen, um zu sehen, ob/wie man's hinkriegt. Das ist auch nichts Schlechtes - im Gegenteil: So lernt man es eigentlich am Besten. Man ist gezwungen, sich selbst Gedanken darüber zu machen, wie man manche Sachen umsetzen würde.

Ich habe mir noch nie Sourcen anderer Leute angeguckt, immer nur die Endprodukte. Sourcen höchstens mal, wenn in einem Forum einer etwas gepostet hat, um Fragen dazu zu stellen. Aber nicht Sourcen von irgendwelchen fertigen Dingen. Irgendwie bin ich nicht gut darin, große Sourcen fremder Leute zu lesen. Das mag auch daran liegen, daß ich mir quasi das allermeiste eher selbst beigebracht habe und daher meinen eigenen eher schrägen Programmierstil gefunden habe. Was (nicht nur) das angeht, bin ich sowas von unprofessionell...
zatzen hat geschrieben:Und für jetzt bedeutet das vielleicht, dass ich zwar immer noch nicht in der Lage bin einen DOS-Computer bis aufs letzte auszureizen,
Naja - wer kann das schon?
Einen C64 oder sowas kann man vielleicht bis ans letzte ausreizen, aber so eine DOS-Kiste... Das ist schon eine Lebensaufgabe. Und da bilden sich manche Leute heutzutage echt ein, die heutigen komplizierten Kisten (und Betriebssysteme) 100% zu verstehen. Ich glaube, nichtmal die Leute, die diese OS coden (bzw. daran beteiligt sind - macht ja keiner mehr alleine) verstehen die zu 100%.
zatzen hat geschrieben: aber seit meinem letzten Großprojekt (Kotzman II), was jetzt 25 Jahre her ist, doch so nach und nach einige Fortschritte gemacht habe und jetzt einfach viel mehr kann, was für mich auch schon ohne Mode-X ein Erlebnis wert wäre.
Klar, man will aus all dem, was man inzwischen gelernt hat, auch mal irgendwann wieder etwas machen, das man auch Leuten zeigen/vorführen/geben kann, die nicht selbst programmieren.
zatzen hat geschrieben: Ich hab zwar jetzt immer gesagt, klar, Mode-X wäre gut und würde ich machen, allerdings ist es ja etwas "krumm" zu programmieren, und es ist vielleicht mit dieser oben diskutieren 8x8 Block-Flag-Technik nicht unbedingt nötig für nicht-Scrollendes, und Mode-X würde mich momentan noch aufhalten etwas zu realisieren.
Naja, wenn man sich erst einmal darauf (Mode-X) eingelassen hat, ist es auch nicht "krummer" als etwas anderes - es ist eben eine Umgewöhnung. Ich neige daher oft eher dazu, Grafik senkrecht zu speichern oder darzustellen, d.h. in dieser Richtung aufzubauen. Aber es ist natürlich klar: Wenn man viel Zeug erst einmal für etwas anderes entwickelt hat, würde einen so ein "kalter Wechsel" erst einmal aufhalten/zurückwerfen.

Ich werde ab und zu mal gefragt, wieso ich, wenn ich Windows nicht mag, die gesamte MS (also auch DOS) Ebene nicht verlasse und auf Linux weitermache. - Das ist der Grund. Weil ich dann eben auch wieder bei Null anfangen müßte - und entweder fremder Leute Libraries benutzen (wie lame ist das denn?) oder wieder von vorne anfangen, sich eigene zu bauen (und dann irgendwann mit über 60 endlich wieder an dem Punkt zu sein, an dem ich unter DOS jetzt bin, um "loszulegen").
zatzen hat geschrieben:Ich kann jetzt definitiv einen ganzen Batzen mehr als vor 25 Jahren und würde auf diesem Fundament gerne ersteinmal etwas errichten.
Das finde ich ja auch eine gute Idee. Mir geht's ja ähnlich.
zatzen hat geschrieben: Mich motivieren derzeit noch so Dinge wie dieses ZVID2 mit den 4x4 Blöcken, und das gestaltet sich in Mode-X leider ziemlich schwer wie ich das einschätze.
Achnaja. Da man bei Mode-X quasi in vertikalen 4er-Einheiten anfängt zu denken, kann das so oder so auch sogar ein Vorteil sein. Kommt drauf an, wie man herangeht. In Mode-X kann man das Chaining ja auch an-/ausschalten. Also kann man mit einem Byte-Zugriff 4 nebeneinanderliegende gleichfarbige Pixel setzen mit einem einzigen Befehl. Oder mit einem 32-Bit Zugriff 4 solcher "4er-Pixel", also quasi 16 Pixel mit einem einzigen Befehl. Stell Dir vor, was das beim Füllen eines Screens ausmacht oder bei einfarbigen Flächen. Außerdem kann man quasi auf 'ne einfache Art "niedrige" und "hohe" Auflösung kombinieren (80x200, 160x200 und 320x200) wie man will.

Also ja: Mode-X ist komisch und vielen wäre lieber, daß man stattdessen 4 "lineare" Bilder als 4 "verschränkte" hätte... Es ist ja auch nur ein "Hack". Und ich will hier auch nicht unbedingt die "Mode-X-Werbetrommel" rühren. Für mich hat es sich irgendwann ergeben, weil ich gut fand, mehrere wechselbare Bilder zu haben ohne zusätzlichen RAM-Verbrauch und ohne "reinkopieren" und ich habe es geschafft, meine Routinen (Level/Sprites/Textausgabe - sogar Proportionalschrift und Multicolor-Schrift) darauf auszulegen. Aber deshalb ist Mode-X trotzdem nicht die Antwort auf alles.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ja, weißt ja: Wenn Hilfe benötigt wird, stehe ich gern mit meinem Wissen zur Verfügung.
Da komm ich gerne nochmal drauf zurück wenn ich z.B. ein Konzept für ein Spiel habe mit Scrolling, was man dann besser mit Pageflip realisiert. Momentan scheint mir ja mein komisches Blockverfahren für nicht-Scroller sogar noch performanter zu sein bei relativ geringem Sprite-Aufkommen.
Ja, aber nur, wenn man dabei "diszipliniert bleibt" (siehe oben). Zuviel anderes Gewurschtel kann einem diesen Vorteil schnell wieder versauen - wär schade drum.
zatzen hat geschrieben:Wenn Du Dich vielleicht mal ein bisschen berieseln lassen willst und Zeit hast, hätte ich hier ein Video, ein Vortrag über die Zukunft des Programmierens, bei dem thematisiert wird wie wichtig es ist, dass wieder wirklich sauber programmiert wird - nicht nur um die Leistung der Systeme optimal auszureizen (von Assembler ist z.B. nicht wirklich die Rede),
Naja, ich gehöre nicht zu den Leuten, die "sauber" programmieren - sondern eher im Gegenteil. Zugunsten von Performance ist mir kein Trick zu dreckig. Und so Kram wie selbstmodifizierender Code und totale Anpassung ans System (was ich beides explizit mache) zählt NICHT als saubere Programmierung, sondern als Gegenteil. "Sauber" Programmiertes Zeug ist gut wartbar/portierbar. Auf so einen Kram habe ich nie Wert gelegt.
zatzen hat geschrieben: sondern weil man als Programmierer, auch wenn man es nicht direkt ahnt, heutzutage oft Verantwortung für Menschenleben hat.
Tja, einem System, das mit Windows (egal welcher Version) läuft, würd ich mein Leben nicht anvertrauen. Diese verbuggte Scheiße ('tschuldige den Ausdruck) könnte leicht jemanden töten - da reicht ein liederlich gebauter Treiber aus um das ganze Kartenhaus einstürzen zu lassen...
zatzen hat geschrieben: Ein schöner Einblick auch in die Geschichte des Programmierens und der frühen Computer.https://youtu.be/ecIWPzGEbFc
Naja, ich guck's mir bei Gelegenheit mal an. Momentan bin ich schon wieder auf'm Sprung...

Re: Eigenes Videoformat

Verfasst: Sa 6. Jun 2020, 21:35
von zatzen
DOSferatu hat geschrieben:
zatzen hat geschrieben:Hallo!

Vielen Dank für die Demo!
Ja, und ich hoffe, Dir ist der Fehler schon aufgefallen.
Nein, ich habe den Code erstmal nur überflogen, richtig reinknien werde ich mich erst wenn ich mich selber an's Programmieren mache. Du hast einen anderen Stil als ich, das ist dann erstmal unübersichtlich, aber auch inspirierend und lehrreich. Aber wo Du es sagst verstehe ich den Fehler (der aber nur ein paar Takte mehr verursacht und sonst nichts macht) natürlich, ist ja logisch.
DOSferatu hat geschrieben:Die habe ich nur so eingebaut, damit man mal die "Klotzkopier-Routinen" in Aktion sieht, weil sie ja sowieso da sind. Im eigentlichen "Spiel" fallen die ja nicht so einzeln auf (was sie ja auch nicht sollen).
Ja, das ist klar. Trotzdem sehe ich da möglicherweise einen Performance-Engpass wenn ein ganzes Bild refresht werden soll. Für Rätselartige Spiele bzw. für Spiele wo ein Level aus nur einem 320x200 Bildschirm besteht kein Problem, da macht man halt zwischendurch Pause wenn's zum nächsten Level geht. Aber wenn ich ein Spiel im Stil von z.B. Prince of Persia machen will, wo zwar nicht gescrollt wird, aber "geblättert", dann habe ich ja auf einmmal 1000 Hintergrundblöcke zu restaurieren und auch 1000 Blöcke in die VGA zu kopieren. Das ist dann sicherlich langsamer als die durchschnittlich vielleicht 200 Blöcke und könnte einen Hänger verursachen. Vielleicht fällt Dir dazu etwas ein. Im Moment hätte ich dazu die Idee dass ich da eben eine Blende einbaue die über 4-5 Frames dauert. Ansonsten muss ich mich eben auf ein 1 Screen per Level Spiel beschränken, Prinzipiell im Stil wie alte Arcades wie Donkey Kong.
Nachtrag: Man könnte immerhin wenigstens den Puffer in die VGA mit einem Rutsch kopieren beim Screenwechsel. Hintergrund auf einen Rutsch wurde nur gehen wenn man überhaupt nichts komprimiert, d.h. auch nicht Redundanz...
DOSferatu hat geschrieben:Immer INC und DEC bevorzugen gegenüber + und -.
Ja, das mach ich schon allein deswegen weil es sich kürzer schreibt. Und jemand, ich glaube sogar Du, hat mir mal erzählt, das würde direkt zu "INC bzw. DEC variable" (also Assembler) kompiliert. PRED und SUCC kannte ich bisher nicht, ich hab immer +/- 1 verwendet und mir nichts dabei gedacht. Zudem war ich erstmal stutzig da PRED eine Funktion genannt wurde, und es wäre ja sehr langsam extra eine Funktion für soetwas aufzurufen. Aber wenn es so wie DEC/INC ist werde ich das mal nutzen demnächst. Zuletzt habe ich auch statt IF variable <> 0 schonmal IF boolean(variable) genutzt. Und manchmal war ich sogar geneigt, statt variable := - variable zu schreiben: asm neg variable end. Ob das einen Vorteil hat?
DOSferatu hat geschrieben:Das war der Plan dahinter, deshalb der Source. Ich dachte so: Ich kann es in elend langen Texten erklären oder ich mache einfach mal ein Programm, das das macht. Dann kann man daran rumspielen und das selbst nachvollziehen.

Auf die VGA werden keine Sprites geschrieben, die kriegt damit immer nur 8x8-Blocks, die dann Teile von Sprites enthalten - bzw. auch Teile, wo im letzten Zyklus ein Sprite war (um das zu löschen). Deshalb auch die 2 Phasen. In jeder Phase wird immer das "Neue" hinzugefügt. Das "Neue und Alte" kopiert, aber für's nächste Mal nur das "Neue" behalten (das dann beim nächsten Mal das "Alte" ist).
Ja, ich hatte das schon ziemlich durchtheoretisiert, mich nur noch nicht bequemt das als Programm umzusetzen, auch weil ich noch hin- und hergerissen war wegen Mode-X, der plötzlich als Option "reinschneite". Deswegen nochmal vielen Dank dass Du Dir die Zeit genommen hast, das einmal konkret niederzuprogrammieren. Ich hätte es wohl direkt fast 100% in Assembler gemacht, hatte schon vorab überlegt wie ich sogar die 1000er Schleife auch beim Spritebereich-Markieren wenigstens horizontal unrolled mache und sowas. Vielleicht unnötig wenn man es ins Verhältnis setzt, dass die Übertragung der Grafikinhalte deutlich länger dauert. Wobei ja doch einige Takte verbraten werden wenn man für jeden einzelnen Block eine Routine aufruft...
DOSferatu hat geschrieben:--- Aber, schon BEVOR getestet ist, ob das alles zusammen dann auch noch einigermaßen läuft, geht es gleich wieder an's "bauen wir eine speicher-/performancefressende intelligente Kompression ein"...
Speicherfressend wohl im Sinne vom Code... Aber natürlich, Du hast mich hier etwas wachgerüttelt von meinen entgleisenden Theorien. RLE war mir selber nicht geheuer, weil man das nicht unrolled entpacken kann (so wie ich das sehe). Bitweise wäre das noch möglich, sicherlich aber ziemlich langsam und mit relativ großen Routinen. Der Gedanke dahinter ist eher noch, dass der zu restaurierende Hintergrund nur ein drittel des ganzen Datentransfers ausmacht. Sprites setzen selber wären "dank" ZVID2 auch nicht das schnellste überhaupt, und in die VGA schreiben ist ebenfalls langsam. Klar da würde man sagen, dann erst recht nicht noch was langsames dazu durch Kompression. Ich denk da nur immer etwas relativ, was beim Programmieren vielleicht falsch ist.
DOSferatu hat geschrieben:Dieses Bild ist keinerlei Referenz für ein Spiel und sollte nicht als Beispiel für irgendwelche Auswertungen/Einschätzungen herangezogen werden. Es war ein zufällig vorhandenes 320x200-Bild, das ich benutzt habe. Kompression in solchen Dingen hat den Nachteil, daß man nie vorher weiß, wieviel Speicher es wirklich belegt. Wenn man Pech hat, erzeugt das gewählte Bild in Kombination mit der gewählten Kompressionsroutine einen Worst-Case und ist am Ende sogar größer als ungepackt.
Sicherlich, für mich war es nur ein Beispiel für nicht-8-Bit-Konsolengrafik sondern 256 Farben Grafik, wie sie auch in einem Spiel von mir vorkommen könnte und mich zufriedenstellen würde. Ist mir einfach aufgefallen dass da 8x8-Blockweise etwas auf 2 oder 4 Farben zu reduzieren möglich wäre. So wie ich das angehen würde hätte es Worst Case 65000 oder 66000 Byte statt 64000, aber auch im "Best Case" irgendwas um die 12000, denn man kann ja auch mit 2 oder 4 Farben schöne dezente Hintergründe zeichnen. Ich will Dich aber auch nicht weiter aufregen.
DOSferatu hat geschrieben:Ein Spieler dankt einem diese Mühe nicht, wenn es am Ende nur ein speicherfressendes ruckeliges Ding ist. Dem ist total egal, wie schick und komplex die Kompression ist. Daher muß man sich da immer die Frage stellen, was das Ziel ist.
Ja, da bin ich etwas befangen. Nur geraderaus ein Spiel umzusetzen nach dem KISS Prinzip passt mir irgendwie nicht so in den Kram. Ich glaube dann würde ich eher zusehen, mit Freepascal ein Windows-Spiel machen zu können (dann hätte ich auch ein großes Publikum an Spielern), oder direkt zu einem überzeugenden Game-Maker greifen (falls es sowas gibt). Aber ich will technisch rumfrickeln, und es sind ja die Realmode-Beschränkungen die mich zu so einem "Blödsinn" inspirieren. Das war auch mal anders, da hab ich auf nem Pentium programmiert, mehrere MB XMS benutzt, da ein komplettes mehrere Screen breites Bild reingeklatscht, 22050 Hz Sound-Samples ebenfalls, schön lang alles, unkomprimiert... So ähnlich könnte ich jetzt auch an ein Spiel rangehen, aber das motiviert mich nicht. Das ist sicherlich aber auch nicht was KISS meint, aber Du siehst schon dass ich irgendwie nen Tick habe Speicher zu sparen, nachdem ich Ende der 90er zu viel davon hatte und das dann irgendwie zu wenig strukurgebend war.
DOSferatu hat geschrieben:Ich selbst würde an so einem frühen Punkt der Spielentwicklung wohl nicht in solche Details abgleiten, wenn die die Dinge, die man eigentlich braucht, noch gar nicht da - und nicht mal angedacht sind.
Ja, wobei die Sache mit den 8x8 Blöcken ja grundlegend ist für das Spiel, das muss ja da sein, sonst kann ich keine Grafik darstellen, klar. Diskutabel ist natürlich das mit der Kompression der Hintergründe, gleich mehr dazu.
DOSferatu hat geschrieben:Also ja: Ich bin immer für die Kachel-Idee, weil die Speicher spart ohne den Aufwand großartig zu erhöhen: Also: Wenn im "Bild" gleiche Kacheln sind, diese zusammenzufassen und nur "Nummern" zu benutzen. Dann können die Kacheln weiterhin mit einer einfachen 32Bit * 16 Kopier-Routine kopiert werden. Wenn man INNERHALB der Kacheln dann aber wieder "packt", dann beraubt man sich diesen Vorteils: Wenn die Kacheln dann wieder einzeln irgendwie "entpackt" werden müssen, gewinnt man nichts mehr an Performance.
Das ist wohl wahr. Allerdings wäre es gleichzeitig regelrecht dumm, diese "Redundanzkomprimierung" nicht zu machen. Und gleichzeitig sehe ich gerade keine andere Möglichkeit, wie man das mit gekachelten Hintergründen und der Block-Flag Methode kombinieren könnte. Levels anhand Kachelsprites zusammensetzen geht schlecht, man muss ja beliebig auf das 8x8 Raster zugreifen können. Da müsste man schon alles auf 8x8 Größe zerlegen und dann größere Blöcke zusammensetzen - okay, möglich. Ansonsten müsste man erst die (> 8x8) Kacheln in den Hintergrundpuffer schreiben und aus diesem restaurieren. Verschwendet aber Speicher.
DOSferatu hat geschrieben:Ja, wie gesagt: Natürlich alles schöne Ideen - und auch wert, darüber nachzudenken. Allerdings vielleicht eine gute Idee, erst einmal ein spielbares kleines Teil zu bauen, um dann zu sehen, ob diese zusätzlichen Features dann noch möglich sind.
Es hält sich schon länger eine gewisse Spielidee in meinem Kopf, eher Arcade, nicht jetzt dieses Sound-Memory von dem ich mal geschrieben habe. Trotzdem werde ich da mit gemütlichen ca. 20 fps hinkommen. Ich habe lange nichts mehr gemacht und bin wohl etwas eingerostet, werde wohl als erstmal mal eine Art Level machen in dem man einfach etwas rumhüpfen darf, vielleicht ein paar Gegner noch die einfach rumlaufen. Das ganze dann mit Sound, dann fehlen nur noch die komplexeren Abfragen. Vielleicht mache ich zwischendrin auch schnell noch einen "Benchmark" der mir zeigt, wieviel fps ich mit Hintergrund komplett in den Puffer kopieren und dann den Puffer in die VGA kopieren hätte. Wenn der dann in Dosbox mit 20000 Cycles 70 fps bzw. mehr schafft kann mir das bzgl. der Blocktechnik zu denken geben, ansonsten sieht es ja positiv aus.
DOSferatu hat geschrieben:Ja, entschuldige meine harten Worte. Ich werde da in letzter Zeit immer pragmatischer.
Da muss ich wohl noch hinkommen. Mir machen Spielereien beim Programmieren immer noch mehr Spaß als das Spielen von Spielen, das ist vielleicht das Problem.
DOSferatu hat geschrieben:... naja, nette Experimente ohne praktischen Nutzen. Da es ein Hobby ist, muß es ja keinen praktischen Nutzen haben, das weiß ich. Aber damit entfernt man sich eben andererseits auch immer mehr vom "Endziel Spiel".
So ziemlich alle meine "Entwicklungen" der letzten Jahre haben keine echte Rechtfertigung, aber sie haben mich motiviert, zu programmieren, und dabei viel zu lernen, das ist dann wiederum der Nutzen. Ich würde es so beschreiben, dass am Ende schon ein Spiel rauskommt, was aber ohne meine Spielereien kurz gesagt besser wäre (performancemäßig vor allem) - aber: Ich bin derjenige der das Spiel machen wird, und der motiviert sich eben aus seinen krummen "Erfindungen" heraus. Kein anderer wird dieses Spiel machen, und ohne meine komischen Formate und Routinen würde es nicht dazu kommen. Ein wenig werden diese Dinge aber auch ins Spiel durchdringen: Ich werde Trackermusik (ZSM) drin haben und Sample-Soundeffekte, und bei der Grafik werde ich nicht kleckern. Allemal werde ich eben keine 70 fps haben, man wird behaupten können es sei ruckelig - ich erwähne aber an dieser Stelle einmal wieder den alten bekannten "Captain Comic", ein Spiel das auch mit 10 fps funktioniert hat und gespielt wurde.
Ich kann mir vorstellen dass Dir das alles gegen die Hutschnur geht so wie ich da rangehe, denn es ist ja irgendwie auf DOS-Ebene durch meinen ganzen Firlefanz irgendwie vergleichbar damit, was heutzutage so in der Windows-Welt los ist, immer langsamer etc., nur dass bei mir der Faktor immer größer nicht dazukommt sondern eher immer kleiner, auf Kosten der Performance.
DOSferatu hat geschrieben:Da gibt es diese Episode von so ehemaligen Microsoft-Mitarbeitern (und auch bei anderen Softwarefirmen) : So "Chefs" und "Manager" sind ja fachlich oft blöde und verstehen nicht wirklich, was ihre Angestellten da eigentlich machen.
Davon wird auch in dem Vortrag in dem Video erzählt. Die Chefs haben keine Ahnung von Softwareentwicklung, deswegen ist es wichtig dass die Programmierer selbstverantwortlich und diszipliniert sind.
DOSferatu hat geschrieben:Irgendwie bin ich nicht gut darin, große Sourcen fremder Leute zu lesen. Das mag auch daran liegen, daß ich mir quasi das allermeiste eher selbst beigebracht habe und daher meinen eigenen eher schrägen Programmierstil gefunden habe. Was (nicht nur) das angeht, bin ich sowas von unprofessionell...
Ich finde mich meistens auch schlecht in anderen Sourcen zurecht, einfach schon weil die einen anderen Stil haben. Deswegen habe ich mich auch in Deinen Democode hier noch nicht so reingekniet, aber er ist natürlich nachvollziehbar. Mir helfen aber solche Dinge wie Einrücken, und bisher habe ich Variablen z.B. immer nur mit einem VAR Statement definiert. Aber vor jede Variable ein VAR zu schreiben kann auch die Übersicht erhöhen... Programmieren als Beruf könnte ich auch schlecht. Es ist nur schön und praktisch grundlegende Dinge zu können um sich hin und wieder Tools zu bauen, oder doch mal irgendwann ein Spiel.
DOSferatu hat geschrieben:Ich werde ab und zu mal gefragt, wieso ich, wenn ich Windows nicht mag, die gesamte MS (also auch DOS) Ebene nicht verlasse und auf Linux weitermache. - Das ist der Grund. Weil ich dann eben auch wieder bei Null anfangen müßte - und entweder fremder Leute Libraries benutzen (wie lame ist das denn?) oder wieder von vorne anfangen, sich eigene zu bauen (und dann irgendwann mit über 60 endlich wieder an dem Punkt zu sein, an dem ich unter DOS jetzt bin, um "loszulegen").
Ja... Ich finde es angenehmer mit dem Können weiterzumachen, was eben schon da ist, und in kleinen Schritten weiterzulernen. Daher sind mir gewisse Dinge noch eine Hürde, gleichzeitig weiss ich aber dass sich eben mittlerweile ein Spiel von mir schon sehen lassen könnte. Das einzige was sein wird, ist dass es wahrscheinlich nur auf 486ern gut laufen wird, obwohl jemand mit mehr Ahnung (oder Vernunft/Disziplin) das auch für den lahmsten 386er hinkriegen würde.
DOSferatu hat geschrieben:Also ja: Mode-X ist komisch und vielen wäre lieber, daß man stattdessen 4 "lineare" Bilder als 4 "verschränkte" hätte... Es ist ja auch nur ein "Hack". Und ich will hier auch nicht unbedingt die "Mode-X-Werbetrommel" rühren.
Ich denke dass ich jetzt erstmal noch mein "Ich kann transparente Grafik, Sample-Sound und überhaupt Assembler"-Abi feiern muss, indem ich damit mal was umfangreicheres anfange. Dann kann Mode-X kommen.
DOSferatu hat geschrieben:[Video]Naja, ich guck's mir bei Gelegenheit mal an.
Ja, ich dachte da nur an Dich, weil da auch bemängelt wird dass heute schlunzig programmiert wird, und auch mehr oder weniger erklärt wird, warum.

Re: Eigenes Videoformat

Verfasst: Sa 6. Jun 2020, 22:54
von zatzen
So, "Benchmark" mal grob gemacht. Ich hatte jetzt keine Lust das genau zu machen und mir den Timer zu erschliessen, sondern hab einfach mal 1000x 64000 Byte von einem Speicherbereich in einen anderen kopiert und den dann nach A000. Gibt hier ca. 300 Durchläufe pro Sekunde d.h. die Schleife dauert etwa drei Sekunden. Da könnte ich jetzt die Flinte ins Korn werfen und sagen das bringt alles nichts mit den 70 fps bei 700 Blöcken. Es zeigt sich aber auch dass alles, wie zu erwarten, entsprechend schneller läuft wenn man den Umsatz reduziert, bei 16000 Bytes (buffer2buffer2vga) * 1000 dauert die Geschichte nur noch ca. eine Sekunde. Ich weiss dass DosBox kein guter Test dafür ist, aber was anderes hab ich gerade nicht.
Du hast in Deinem Demo-Code die ganzen Blockmarkierungen in reinem Pascal mit Schleifen und IF umgesetzt, und es sind Routinen mit Stackframe (bzw. intern deklarierten Variablen). Du sagtest selber dass man da noch optimieren kann wenn man es in Assembler umsetzt, möglicherweise wird es dann schnell genug, dass sich die Sache wirklich lohnt. Ich hatte jetzt erst gedacht, Du rufst für jeden einzelnen Block eine Routine auf, aber das ist ja nicht so. Ausser bei RecoverBlock. Und BlockToVGA. Also doch... Da sehe ich noch Optimierungmöglichkeiten, so eine Pascal-Routine hat ja durchaus einen gewissen Overhead und es dürften dann bei 700 Blöcken schon einige (wohl zweistellig-)tausend vermeidbare Takte zusammenkommen. Also danke für die Übersichtlichkeit durch die Strukturierung in freundlichem Pascal und Gliederung in Routinen, ich hatte nur von Anfang an gedacht, z.B. den ganzen Block2VGA-Schmonzius von einer einzigen evtl. teilweise entrollten ASM Routine erledigen zu lassen.
Was man noch testen könnte wäre noch, die Demo so umzubauen dass sie "herkömmlich" einfach den kompletten Hintergrund in den Puffer kopiert zum restaurieren, dann kommen die Sprites, dann komplett rein in den VGA Speicher. Kann ich ja mal machen, und dann wird man sehen bis zu welchem Punkt, unter gleichen Bedingungen (ohne Sound etc.) welche Methode vorteilhafter ist. Bei 700 Blöcken wird einfaches kopieren schneller sein, und Vorteile von der Blocktechnik werden sich nur bei wenigen Cycles zeigen.

Hab jetzt einen kleinen Fehler(?) gefunden, die Routine UnMarkSpr wird gar nicht benutzt.

So, ich habe jetzt mal das ganze Blockkonstrukt aus der Frame-Routine entfernt und durch einfaches Pointer2Pointer-Kopieren ersetzt. Es ist auf der ganzen Linie etwas schneller, bei 3000 Cycles bis 12 Sprites mit 70 fps, bei 20000 Cycles volle Fahrt bis 255 Sprites. Dass also die Blockmethode so abstinkt, kann dran liegen dass die Idee einfach nicht gut ist, oder eben an der Umsetzung, die mit IFs, Schleifen und Routinen mit Stackframe/Overhead(kläre mich gerne auf wie man das besser nennt, was ich meine ist, dass es keine simplen Routinen sind die nur ne handvoll Takte fressen wenn sie gerufen werden und sich verabschieden) einfach deutlich langsamer ist als wenn man das minimalistisch in Assembler umsetzt. Völlig klar, dass Du es mit Absicht exemplarisch in fluffigem Pascal gehalten hast, aber vielleicht verzerrt es etwas die Tatsachen wenn man genau das Ding als Untersuchungsobjekt nimmt.
Eigentlich wollte ich mich noch über die Verwendung zweidimensionaler Arrays "beklagen", da ich damit die Erfahrung gemacht hatte dass Pascal dann zum Adressieren Orgien mit MUL kompiliert. Ich habe das hier mit einem 25x40 Array aber mal getestet und es kommt raus:

Code: Alles auswählen

Pascal Code:
mx[24, 39] := 255;
test := mx[24, 39];
  
Kompilat:
0x0000000000000000:  C6 06 37 04 FF    mov byte ptr [0x437], 0xff
0x0000000000000005:  A0 37 04          mov al, byte ptr [0x437]
0x0000000000000008:  A2 38 04          mov byte ptr [0x438], al
Mit http://shell-storm.org/online/Online-As ... assembler/ habe ich endlich mal einen Disassembler gefunden, der auch noch 16 Bit kann. Du hast wahrscheinlich längst einen guten für Dos.

Kaum das geschrieben, dünkt mir dass man ja keine Konstanten hat zur Adressierung sondern Variablen.
Und jetzt sieh Dir diese Katastrophe an:

Code: Alles auswählen

Pascal Code:
mx[x,y] := 255;
test := mx[x,y];

Kompilat:
0x0000000000000000:  A0 3A 04          mov al, byte ptr [0x43a]
0x0000000000000003:  30 E4             xor ah, ah
0x0000000000000005:  8B C8             mov cx, ax
0x0000000000000007:  A0 39 04          mov al, byte ptr [0x439]
0x000000000000000a:  30 E4             xor ah, ah
0x000000000000000c:  BA 28 00          mov dx, 0x28
0x000000000000000f:  F7 E2             mul dx
0x0000000000000011:  8B F8             mov di, ax
0x0000000000000013:  03 F9             add di, cx
0x0000000000000015:  C6 85 50 00 FF    mov byte ptr [di + 0x50], 0xff
(hier geht die Adressierung wieder von vorne los, das gleiche nochmal für's Lesen...)
0x000000000000001a:  A0 3A 04          mov al, byte ptr [0x43a]
0x000000000000001d:  30 E4             xor ah, ah
0x000000000000001f:  8B C8             mov cx, ax
0x0000000000000021:  A0 39 04          mov al, byte ptr [0x439]
0x0000000000000024:  30 E4             xor ah, ah
0x0000000000000026:  BA 28 00          mov dx, 0x28
0x0000000000000029:  F7 E2             mul dx
0x000000000000002b:  8B F8             mov di, ax
0x000000000000002d:  03 F9             add di, cx
0x000000000000002f:  8A 85 50 00       mov al, byte ptr [di + 0x50]
0x0000000000000033:  A2 38 04          mov byte ptr [0x438], al
Da kann man bestimmt massiv Takte sparen wenn man es anders angeht.

Ich mach es mal in Assembler und dann sehen wir weiter.
Wenn es ein Schuss in den Ofen wird, wäre Mode-X konsequent.

Re: Eigenes Videoformat

Verfasst: So 7. Jun 2020, 16:33
von DOSferatu
zatzen hat geschrieben:
DOSferatu hat geschrieben:Die habe ich nur so eingebaut, damit man mal die "Klotzkopier-Routinen" in Aktion sieht, weil sie ja sowieso da sind. Im eigentlichen "Spiel" fallen die ja nicht so einzeln auf (was sie ja auch nicht sollen).
Ja, das ist klar. Trotzdem sehe ich da möglicherweise einen Performance-Engpass wenn ein ganzes Bild refresht werden soll. [...]
Nachtrag: Man könnte immerhin wenigstens den Puffer in die VGA mit einem Rutsch kopieren beim Screenwechsel. Hintergrund auf einen Rutsch wurde nur gehen wenn man überhaupt nichts komprimiert, d.h. auch nicht Redundanz...
Ähm... - Dir ist aber schon aufgefallen, daß die entsprechende Routine zum Kopieren von Pointer nach Pointer schon DRIN ist, oder? Wie gesagt, das "Mosaik-Kopieren" habe ich nur zusätzlich gemacht.
Die Routine heißt CopyPtr2Ptr. Und die benutzt selbstredend natürlich die bekannte Variante, 16000 DWords mit REP;MOVSD zu kopieren.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Immer INC und DEC bevorzugen gegenüber + und -.
Ja, das mach ich schon allein deswegen weil es sich kürzer schreibt. Und jemand, ich glaube sogar Du, hat mir mal erzählt, das würde direkt zu "INC bzw. DEC variable" (also Assembler) kompiliert. PRED und SUCC kannte ich bisher nicht, ich hab immer +/- 1 verwendet und mir nichts dabei gedacht. Zudem war ich erstmal stutzig da PRED eine Funktion genannt wurde, und es wäre ja sehr langsam extra eine Funktion für soetwas aufzurufen.
Ja, bei PRED / SUCC kommt es auf den jeweiligen Anwendungsfall an. Wenn ich es innerhalb eines Ausdrucks benutze und so. Da dies ja "interne" Funktionen von Pascal sind, wird dafür NICHT extra wirklich irgendwo ein Funktionskopf und ähnliches erzeugt.
zatzen hat geschrieben:Aber wenn es so wie DEC/INC ist werde ich das mal nutzen demnächst. Zuletzt habe ich auch statt IF variable <> 0 schonmal IF boolean(variable) genutzt. Und manchmal war ich sogar geneigt, statt variable := - variable zu schreiben: asm neg variable end. Ob das einen Vorteil hat?
Ich denke, das hat einen Vorteil. Und, noch eine kleine (vielleicht hilfreiche) Anmerkung: Wie wir alle wissen, kennt unser geliebtes Turbo-Pascal und sein Compiler nichts, was nach 286er kam - was bedeutet, daß alles, was 32 Bit braucht (wie LONGINT) immer mit 2 Words abgearbeitet wird - jede Addition/Subtraktion/Multiplikation/Division, an der LONGINTs beteiligt sind, werden natürlich durch den Compiler quasi "zweigeteilt". In diesem Fall hat es definitiv einen Vorteil, wenn man Assembler benutzt mit entsprechenden 32 Bit Varianten.
Allerdings muß man dabei auch immer sehen, an welcher Stelle man was einsetzt. Außerhalb von Schleifen, an irgend einem "Vorbereitungs-Code" spart man vielleicht 5-10 Zyklen. Innerhalb einer Schleife, die 1000x durchlaufen wird, spart man dann das 1000-fache. Man KANN natürlich ALLES bis ins Kleinste schneller/kürzer machen (und meine 100%-ASM-Engines machen das zum Teil auch), aber muß auch nicht. Es gibt auch Stellen, wo ich mich für Verschwendung von ein paar Zyklen zugunsten von Speichersparen entscheide - und andere, wo ich absichtlich Speicher verschwende, wenn es mir ein paar Zyklen spart. Das mache ich von der Situation abhängig.

Zu boolean: Es gibt ja Typ boolean (1 Byte), wordbool (2 Byte), longbool (4 Byte). Man kann mit dem "absolute" Konstrukt ja auch Variablen "über" andere Variablen setzen.
z.B. String:
var h:string; lh:byte absolute h; hx:boolean absolute h;
Schon kann man mit hx prüfen, ob ein String leer ist oder nicht oder mit lh direkt die Länge ermitteln (und nicht nur das - sondern auch ändern!).

Alles was in einem boolean nicht 0 ist, wird als WAHR gewertet. Man muß dann nur vorsichtig sein, wenn man solche "absolut" booleans anderweitig als Argument benutzt (z.B. in array[false..true]).

Oder prüfen, ob Pointer gesetzt (ich initialisiere die ja immer gleich als NIL).
const P:Pointer=nil; var PX:longbool absolute P;
Dann kann man etwas auf diesen Pointer Bezogenes ausführen mit:
if PX then...
zatzen hat geschrieben:[...]Ja, ich hatte das schon ziemlich durchtheoretisiert, mich nur noch nicht bequemt das als Programm umzusetzen, auch weil ich noch hin- und hergerissen war wegen Mode-X, der plötzlich als Option "reinschneite". Deswegen nochmal vielen Dank dass Du Dir die Zeit genommen hast, das einmal konkret niederzuprogrammieren. Ich hätte es wohl direkt fast 100% in Assembler gemacht, hatte schon vorab überlegt wie ich sogar die 1000er Schleife auch beim Spritebereich-Markieren wenigstens horizontal unrolled mache und sowas. Vielleicht unnötig wenn man es ins Verhältnis setzt, dass die Übertragung der Grafikinhalte deutlich länger dauert. Wobei ja doch einige Takte verbraten werden wenn man für jeden einzelnen Block eine Routine aufruft...
Mir ist das alles durchaus bewußt. Wie gesagt: Pascal nur der Übersichtlichkeit wegen - damit man sieht, wann und womit ein Block aufgerüfen wird. Dir ist ja sicher aufgefallen, daß der Block selbst KEINE Multiplikation benutzt, um den Offset im Speicher zu berechnen, sondern mit ein paar einfachen Additionen/Shifts das Gleiche hinkriegt. (Am Anfang der Block-Kopier-Routinen.)

Also: Ja, natürlich würde man im echten Prozeß NICHT einzeln so eine Routine aufrufen, sondern sie gleich in die 1000er Schleife integrieren und entweder durch Übersprung (wenn nicht benutzt) ODER Raus-/+Rück-Sprung (wenn benutzt) "aufrufen". Hatte ja geschrieben, daß diese Pascal-Sachen da nur dazu dienen, es erstmal besser nachvollziehen zu können.
zatzen hat geschrieben:
DOSferatu hat geschrieben:[...]"bauen wir eine speicher-/performancefressende intelligente Kompression ein"...
Speicherfressend wohl im Sinne vom Code...
Ja, im Sinne von Code.
zatzen hat geschrieben:Aber natürlich, Du hast mich hier etwas wachgerüttelt von meinen entgleisenden Theorien. RLE war mir selber nicht geheuer, weil man das nicht unrolled entpacken kann (so wie ich das sehe). Bitweise wäre das noch möglich, sicherlich aber ziemlich langsam und mit relativ großen Routinen. Der Gedanke dahinter ist eher noch, dass der zu restaurierende Hintergrund nur ein drittel des ganzen Datentransfers ausmacht. Sprites setzen selber wären "dank" ZVID2 auch nicht das schnellste überhaupt, und in die VGA schreiben ist ebenfalls langsam. Klar da würde man sagen, dann erst recht nicht noch was langsames dazu durch Kompression. Ich denk da nur immer etwas relativ, was beim Programmieren vielleicht falsch ist.
Naja, die Sprites setzt man ja mit dem Verfahren nie direkt in die VGA, sondern in die "Zwischen-Ebene". Wie ich schonmal versucht habe zu erklären, hat das ganze hier 3 Ebenen:

"Unterste" ist das Hintergrundbild. Das wird nie geändert - das ist nur dazu da, immer wieder den Grundzustand herzustellen, wenn etwas überschrieben wurde, d.h. aus dieser Ebene wird nur gelesen.

"Mittelste" ist die "Temp"-Ebene. Die enthält zu Anfang eine Kopie des Hintergrundbilds. Da werden die Sprites draufgeklatscht, bei gleichzeitiger Markierung der 8x8-Blocks. Hier werden im nächsten Zyklus auch die 8x8-Blocks wieder hergestellt - indem sie aus der "untersten" zurückgeholt werden.

"Oberste" ist die VGA-Ebene, d.h. der direkte VGA-Speicher ab $A000:0. Hier wird nur aus der "mittelsten" (der "Temp") Ebene reinkopiert, und nur 8x8-blockweise - und zwar nur die "Änderungen". Das mit den Wechselphasen dient dazu, daß die Blocks, wo die Sprites "mal gewesen" sind, aber jetzt nicht mehr sind, nur einmalig kopiert werden. Es werden aber ständig alle Sprites-enthaltenen Blöcke kopiert - auch bei denen, die ihre Position nicht ändern. Das könnte man noch anpassen, aber:

Wichtig bei "stillen" Sprites muß dann sein, daß sie WEDER ihre Position, NOCH ihr Aussehen ändern. Ein Sprite, das die gleichen Blocks belegt wie vorher, sich aber um 2 Pixel bewegt - ohne daß die belegten Blocks sich dabei ändert - und/oder sich gar nicht bewegt, aber eine andere Animationsphase hat, muß trotzdem kopiert werden. Außerdem muß berücksichtigt werden, daß sich Sprites auch "treffen" können, d.h. ein bewegliches kann ein unbewegliches treffen und VOR oder sogar HINTER dem anderen durchziehen/durchlaufen... All diese Dinge müssen berücksichtigt werden, daher sollte man, wenn man eine "stilles Sprite = nicht kopieren" Funktion SEHR GENAU auf die genannten Eventualitäten abgleichen, damit einem nicht später irgendwelche "abgeschnittenen Sprites" begegnen und man sich fragt woher das kommt.

Wie ich ja bereits geschrieben habe und noch einmal schreibe: Das war nur eine allererste Demo, die das Grundprinzip in Funktion darstellt, da ist nichts optimiert außer die Blockkopierroutinen selbst - und NATÜRLICH ist eine 16000-DWORD Kopierroutine (REP;MOVSD) schneller als 1000 Block-Aufrufe.

Hinweis: Das "Mosaik" habe ich aber übrigens extra langsam gemacht, wegen des Effekts. Weiß nicht, ob Du das siehst, daß ich da mit dem Ticker extra 'ne Wartefunktion eingebaut habe, die alle 32 Blocks auf den nächsten Tick wartet. Ohne diese wäre es nämlich selbst mit 1000 Blockaufrufen UND dem ganzen Random-Kram trotzdem auf 486er gar nicht zu sehen, daß das "rein-mosaikisiert". Das ist also nicht wirklich so "langsam" - ich habe das ausgebremst.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Dieses Bild ist keinerlei Referenz für ein Spiel und sollte nicht ...[...]
Sicherlich, für mich war es nur ein Beispiel für nicht-8-Bit-Konsolengrafik sondern 256 Farben Grafik, wie sie auch in einem Spiel von mir vorkommen könnte und mich zufriedenstellen würde. Ist mir einfach aufgefallen dass da 8x8-Blockweise etwas auf 2 oder 4 Farben zu reduzieren möglich wäre. So wie ich das angehen würde hätte es Worst Case 65000 oder 66000 Byte statt 64000, aber auch im "Best Case" irgendwas um die 12000, denn man kann ja auch mit 2 oder 4 Farben schöne dezente Hintergründe zeichnen.
Wie gesagt, blockweises "Packen", d.h. gleiche Blocks im Bild finden und nur 1x benutzen und dann mit Tabelle nur die Blocknummer nennen, würde fast nichts verlangsamen, dafür könnte es viel Speicher sparen (je nach Bild), da es ja sowieso "blockartig" benutzt wird, würde das lediglich eine bessere Ausnutzung der ohnehin vorhandenen "Block-Kopier-Vorgehensweise" sein (und dabei auch Speicher sparen).

Und die gekachelten Levels auf alten Konsolen (und auch, wie ich es in meinen Spielen verwende) dienen ja auch hauptsächlich dazu, große Levels zu haben und dabei Speicher zu sparen. Hätte Nintendo die Levelhintergründe von SUPER MARIO BROS. als scrollende Vollgrafik machen wollen, hätten sie mit diesen 48 kByte, die sie hatten, niemals so ein umfangreiches Spiel machen können.
zatzen hat geschrieben:Ich will Dich aber auch nicht weiter aufregen.
Achnaja... "aufregen"... - Wie gesagt, mir ging es nur darum, daß ich es schade finde, wenn man irgendwann eine elegante schnelle Methode findet, um (wenn man dann auch das "Umliegende" alles in 100% umgesetzt hätte) eine wirklich schöne schnelle Variante zu finden, um ein flüssiges Spiel zu haben und das Ganze schon, bevor es soweit ist, wieder "kaputtdenkt", so daß man diesen ganzen Geschwindigkeitsgewinn nur dazu braucht, um wieder komplizierte Packalgorithmen zu benutzen, damit es genauso ruckelig wird, wie es wäre, wenn man jedesmal den ganzen Screen neuzeichnen würde.

Natürlich ist Speichersparen IMMER eine gute Idee - sehe ich ja genauso. Und die o.g. "gleiche Blöcke nur 1x speichern" wäre ja auch dafür angetan. Aber wenn man quasi "zu sehr" abgeht dabei, kann es passieren, daß die Packroutine und eventuelle Tabellen, die man braucht, um Speicher zu sparen, zusammengenommen insgesamt mehr Speicher belegen könnten als man insgesamt spart.

Man muß ja immer daran denken, daß Code nicht einfach "irgendwo anders" ist, sondern den gleichen Heap belegt wie die Daten (wir haben ja Von-Neumann-Rechner hier) und Code/Tabellen u.ä. den gleichen RAM belegen wie es die "Daten" tun.

Natürlich ist das alles "Zetern auf hohem Niveau" meinerseits, denn mein Zeug ist auch groß und langsam - habe noch nie ein 70-FPS-Spiel gebaut - nichtmal auf 486er läuft mein Zeug auch nur annähernd ruckelfrei (siehe Xpyderz) : Die Level-Routinen sind 16 Jahre alt und da wäre noch viel Gelegenheit zum Optimieren gegeben (vor 16 Jahren waren meine ASM-Kenntnisse noch nicht so wie heute), ....

... und von den aufgeblasenen Sprite-Routinen will ich mal gar nicht reden. Dadurch, daß diese so viele Features haben, sind sie natürlich auch nicht so schnell wie sie ohne diese sein könnten - will sagen: Auch ohne Nutzung der Features bremsen diese die Sprites aus. Es gibt da zwar einige "Sub-Schleifen", die je nach genutzter Kombination bestimmte Dinge weglassen - aber das "kann in alle Richtungen gedreht/gespiegelt werden" ist IMMER da! D.h. wenn ich z.B. eine Routine bauen würde, die ein Sprite nur in 90°-Schritten drehbar und spiegelbar macht und/oder skalierbar... oder z.B. nur in ganzzahligen Schritten skalierbar --- selbstverständlich wären dann die Spriteroutinen MINDESTENS doppelt so schnell! (Außerdem enthalten die Sprite-Routinen noch so einiges an Pascal-Zeug, was man sicher auch noch in ASM umsetzen könnte/sollte.)

Ich bin nur froh, überhaupt mal Level-/Sprite-Routinen zu HABEN, die erstmal irgendwas können. Außerdem werden die ja auch nur aufgerufen, wenn mindestens ein Pixel des Sprites im sichtbaren Bildbereich ist - das "Clipping" war ja von Anfang an fest eingebaut.

(Zur Erklärung: Meine Sprites werden nicht wie ein klassisches Sprite behandelt, sondern entsprechen eher der Idee einer durch ein gedrehtes Polygon - in dem Fall Rechteck - "ausgeschnittenen" Textur. Auf so Ideen kommt man wohl erst, wenn man - wie ich - auch schon so 3D-Zeug gemacht hat. Habe ja so 2000/2001 Routinen/Testprograme gebaut, mit denen man in 3D durch Doom-Levels laufen kann - inkl. Texturen und Beleuchtung.)
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ein Spieler dankt einem diese Mühe nicht, wenn es am Ende nur ein speicherfressendes ruckeliges Ding ist. Dem ist total egal, wie schick und komplex die Kompression ist. Daher muß man sich da immer die Frage stellen, was das Ziel ist.
Ja, da bin ich etwas befangen. Nur geraderaus ein Spiel umzusetzen nach dem KISS Prinzip passt mir irgendwie nicht so in den Kram.
Ja, ich weiß: Der Weg ist das Ziel. Und ich will Dir das Ganze auch gar nicht ausreden oder so. Ich referenziere hier nur aus meinen Erfahrungen. Mir ist es schon manchmal passiert, daß ich Zeug gebaut habe, bei dem ich dachte: "Na, hier müßte man aber mal packen, um Speicher zu sparen, das sieht alles so packbar aus." und hatte dann bitbasierte, mit vorberechneten Tabellen versehene und auch einigermaßen schnelle Packroutinen gebaut und das Zeug funktionierte dann auch - aber: Es war am Ende etwas langsamer und hatte vielleicht 5% Speicher gespart - weil die Packroutinen und Tabellen usw. ja dann auch Speicher und Rechenzeit brauchten.

So ähnlich ging's mir (und da noch viel schlimmer) mit so seriellen Übertragungsprotokollen, die ich entwickelt hatte: Irgendwann zu groß und umständlich, mit viel "Fehlerkorrektur"-Möglichkeiten... aber ebenfalls nicht gefeit, auf normale Timeouts zu reagieren (wenn Pakete/Daten fehlen)... Die haben sich dann irgendwann hochgeschaukelt - zum Ende war die Menge der Fehlerkorrekturdaten und -pakete ungefähr 5-mal so hoch wie die eigentlich zu übertragende Datenmenge.
zatzen hat geschrieben:[...]Freepascal ein Windows-Spiel machen[...]überzeugenden Game-Maker[...] Aber ich will technisch rumfrickeln, und es sind ja die Realmode-Beschränkungen die mich zu so einem "Blödsinn" inspirieren. Das war auch mal anders, da hab ich auf nem Pentium programmiert, mehrere MB XMS benutzt, da ein komplettes mehrere Screen breites Bild reingeklatscht, 22050 Hz Sound-Samples ebenfalls, schön lang alles, unkomprimiert... So ähnlich könnte ich jetzt auch an ein Spiel rangehen, aber das motiviert mich nicht.
Ja, wie gesagt: Ich verstehe das. Bei mir liegt es wahrscheinlich an der inzwischen komplett unterschiedlichen Herangehensweise. Das was ich so momentan mache, ähnelt schon mehr einem Game-Maker als einem Spiel: Ich baue viel "generalisiertes" Zeug, das unter gegebenen Umständen eingebaut werden kann und zu funktionieren hat. Ich gehe (blasphemischerweise!) auch öfter dazu über, Dinge modular zu gestalten. Modulare Programmierung hat ja den Nachteil, daß mitunter - wegen der "weniger dichten Integration" manche Sachen etwas Rechenzeit kosten. Dem wirke ich nur dadurch entgegen, daß meine "Module" mehr und mehr zu monolithischen 100%-Assembler-Blöcken werden, die wenigstens für sich selbst gesehen dicht integriert sind.

Will sagen: Mein ganzes Konzept besteht eher aus vielen ASM-Monolithen, die das, was sie können sollen, auch ganz gut können - und einem Hauptprogramm, das nichts weiter macht, als die Kommunikation zwischen den Schnittstellen dieser Monolithen zu übernehmen/überwachen.

Auf einem C64 oder irgend einer anderen 8-Bit-Schüssel (oder Konsole) würde ich das nie im Leben so machen, weil man da ständig an Speicher- oder Performancegrenzen stoßen würde!

Aber mein Ansatz zielt darauf ab, daß ich einmal geschriebenen und funktionierenden Code oder Teil-Code wiederverwendbar machen will, so daß ich nicht für jedes eventuelle Spiel wieder "bei Null anfangen" müßte. Das liegt auch daran, daß sich die von mir favorisierten Spielgenres scrollende Jump'n'Run und Shoot'em'up technisch ziemlich gleichen und sich mit den gleichen Engines hier eine Menge solcher Spiele bauen ließen, weil die sich zwar von Grafik/Sound/Steuerung unterscheiden, aber von der grundlegenden Spielmechanik nicht.

Weil Scrolling, (bzw. mehr als bildschirmgroße Levels) schon immer eher das war, was ich machen wollte, waren für mich auch "nur ungeänderte Bildbereiche restaurieren"-Funktionen nie eine Frage, weil das bei Scrolling kaum noch Vorteile mehr bringen würde. Es ginge zwar trotzdem - nämlich, wenn man nur Level und Sprites hat - aber sobald es mehrere scrollende Hintergründe gibt, bringt es quasi gar nichts mehr. (Anm.: Meine Levelroutinen erlauben bis zu 4 unterschiedlich scrollende Hintergründe und als quasi "5. Ebene" Sprites - lustigerweise habe ich später herausgefunden, daß das genausoviel ist, wie das SNES hat.)
zatzen hat geschrieben:Das ist sicherlich aber auch nicht was KISS meint, aber Du siehst schon dass ich irgendwie nen Tick habe Speicher zu sparen, nachdem ich Ende der 90er zu viel davon hatte und das dann irgendwie zu wenig strukurgebend war.
Ja, wie gesagt: Sei Dir ja auch alles gegönnt. Ich wollte lediglich anmerken, daß es schade wäre, wenn man bei all der Mühe am Ende nicht mehr im Ergebnis hätte, das man auch ohne die Mühe hätte, wenn man einfach stumpf das Bild jedesmal 100% neuzeichnen würde.

Ich meine damit das "Zeichne großes Bild auf 20 m²-Wand" Ding: Ab und zu, wenn ich mal eine gute Idee zu meinen habe und mich darin tage-/wochenlang verliere, gehe ich auch mal "einen Schritt zurück" (von der "Wand") und gucke mir das "Gesamtergebnis" an - ob es noch das ist, was ich eigentlich wollte...
zatzen hat geschrieben:
DOSferatu hat geschrieben:Also ja: Ich bin immer für die Kachel-Idee, weil die Speicher spart ohne den Aufwand großartig zu erhöhen: Also: Wenn im "Bild" gleiche Kacheln sind, diese zusammenzufassen und nur "Nummern" zu benutzen. Dann können die Kacheln weiterhin mit einer einfachen 32Bit * 16 Kopier-Routine kopiert werden. Wenn man INNERHALB der Kacheln dann aber wieder "packt", dann beraubt man sich diesen Vorteils: Wenn die Kacheln dann wieder einzeln irgendwie "entpackt" werden müssen, gewinnt man nichts mehr an Performance.
Das ist wohl wahr. Allerdings wäre es gleichzeitig regelrecht dumm, diese "Redundanzkomprimierung" nicht zu machen.
Richtig. Wenn man sowieso Kacheln/Blöcke o.ä. hat, könnte wenigstens das "Hauptbild" entsprechend gepackt sein. Das "Temp-"Bild (das "mittlere", siehe oben) kann man nicht packen, weil es ja die "aktive Fläche" ist, von wo aus nach VGA kopiert wird und wo die Sprites "unvorhersehbar" gesetzt/gelöscht werden.

Das "Hauptbild" sollte dann aber auch "packbar" sein. Und ja: Natürlich könnte man beim Hauptbild so Dinge wie "einfarbige" Blocks entsprechend markieren. Beispiel: Man hat 1000 Zeiger auf Blocks, jeder ist ein Word groß und zeigt auf die Stelle, wo der Block liegt (damit kann man "gleiche" dann doppelt nennen aber nur 1x speichern). Diese Blocks können maximal 64000 Positionen haben (von 0 bis 63935). und man hätte immer noch mindestens 1536 Werte, die nichts bedeuten. Wenn oberes Byte also 250 bis 255, könnte das untere Byte irgend etwas sein, wie z.B. eine Farbe. D.h. wenn da steht $FFxx, dann ist es keine Blockposition, sondern bedeutet, daß an der Stelle ein 8x8-Bereich kommt der komplett Farbe xx hat. Und der wäre natürlich schneller zu pixeln, weil nichts "geholt" werden muß.

Und ja, weil der Pointer für die ungepackten Blocks natürlich immer an 64er-Positionen landet, bräuchte er eigentlich nur 10 Bit sein (000-999) und man könnte mit den restlichen 64536 Werten machen was man will... Hier könnte man verschiedentlich abgehen. Es sollte dabei dann eben nur darauf geachtet werden, daß wenn ein 8x8-Block selbst aus gepackten Daten besteht, diese nicht dermaßen kompliziert gepackt sind, daß der Speed davon in den Keller geht.

Außerdem ist es gar nicht mal so einfach, gerade mal 64 Bytes so zu packen, daß die "Meta-Daten" (Header, "Tabellen" usw.) am Ende so viel weniger belegen, daß es sich lohnt. Und, was dazukommt ist: Sobald etwas byte-weise gepackt ist, verliert man den Geschwindigkeitsvorteil der 32-Bit-Zugriffe, d.h. die Anzahl auszuführender Befehle wird mindestens vervierfacht. (Mindestens, weil ja zusätzlich die Entpack-Dinge bearbeitet werden müssen.)
zatzen hat geschrieben:Und gleichzeitig sehe ich gerade keine andere Möglichkeit, wie man das mit gekachelten Hintergründen und der Block-Flag Methode kombinieren könnte. Levels anhand Kachelsprites zusammensetzen geht schlecht, man muss ja beliebig auf das 8x8 Raster zugreifen können.
Naja, wie gesagt: Das "Hauptbild" könnte aus 1000 "Zeigern" bestehen, die auf die wirklichen Blöcke zeigen. Bei gleichen Blöcken ist der Zeiger gleich. Und da hier noch etwas "Luft" ist, hätte man zusätzlich noch Optionen, um gleich im "Zeiger" auch noch "Vorgaben" für verschiedene Arten von Packen zu haben (siehe oben).

Das von mir vorgestellte kleine Demo, abgesehen davon, daß da - bis auf die Blockkopierroutinen - absichtlich NICHTS OPTMIERT ist, zeigt nur die ursprüngliche Idee ohne jeden "Zusatz".
zatzen hat geschrieben:Da müsste man schon alles auf 8x8 Größe zerlegen und dann größere Blöcke zusammensetzen - okay, möglich. Ansonsten müsste man erst die (> 8x8) Kacheln in den Hintergrundpuffer schreiben und aus diesem restaurieren. Verschwendet aber Speicher.
Ja, wie gesagt. Das "Hintergrundbild" kann aus Zeigern auf Blöcken bestehen. Selbst wenn es "ungepackt" wäre, wären es 66000 Bytes (64000 für Blöcke, 2000 für Zeiger). Wenn man immer 2 Zeiger auf 3 Bytes verteilt oder (weil man ja nur 10 Bit braucht) 4 Zeiger auf 5 Bytes, wären es bei 2 auf 3 nur 1500 Bytes für Zeiger (also schon unter 1536) oder bei 4 auf 5 nur 1250 Bytes - wären also Daten plus Zeiger selbst wenn "nicht packbar" noch in einem 64k-Segment möglich...

Es ist nicht so, als hätte ich keine Idee, ob und wie man hier Speicher sparen könnte. Es ist nur, wie ich ja schonmal sagte, auch immer ein Trade-Off zwischen Speichersparen und Geschwindigkeitkriegen. Und weil es ja darum ging, durch das "nicht ganzen Screen neuzeichnen müssen" die Framerate zu erhöhen, ging es mir nur erst einmal darum. Jede Einsparung am einen Ende, sorgt für "Opfer" am anderen Ende.

Beim PC kann man da leider auch nicht wirklich in eine bestimmte Richtung optimieren, weil man nicht weiß, was hier die Bremse ist, weil jeder PC anders ist - Beispiel: Bei PC mit schnellem VGA-Durchsatz aber langsamer CPU müßte man in die entgegengesetzte Richtung optimieren als bei PC mit langsamer VGA und schneller CPU. Wenn beides langsam ist, ist da wieder eine mittlere Speed-Optimierung am besten und wenn beides schnell ist, aber wenig Speicher da, optimiert man auf Speichersparen und nicht auf Geschwindigkeit...

Der Königsweg wäre, auswechselbare Routinen zu haben, zu Anfang die Ressourcen zu messen und danach dann die jeweils beste Routine zu laden, die für die vorhandene Systemkonfiguration geeignet ist. ... - Ja, es ist nicht so, als hätte ich mir über gewisse Dinge nicht auch schon Gedanken gemacht... hihi...

Aber irgendwann, wenn man so ein alter Sack ist wie ich, kommt man an den Punkt, wo man einfach merkt, daß man eigentlich schon wieder gar nichts schafft, weil man nicht einmal IRGEND ETWAS "spiel-mäßiges" baut, sondern schlichtweg GAR NICHTS. Und dann erinnert man (ich) sich daran, daß man mit Xpyderz nur so weit gekommen ist, weil man "einfach mal gemacht" hat und hinterher angefangen hat, zu optimieren, sowohl bei Speicher als auch bei Speed.

Momentan (wie bereits schon zu oft erwähnt) bin ich - wenn ich ÜBERHAUPT mal dran was mache - eher dabei, an diesem Zeug rumzubauen, aber nicht an einem Spiel selbst. EIGENTLICH bin ich nämlich, was die innere Spielmechanik angeht, sogar inzwischen fertig! Ja, richtig gelesen: FERTIG! - Ich könnte quasi noch HEUTE damit anfangen, ein Spiel damit zu machen! Das Ding kann Levels und Sprites darstellen, Musiken und Soundeffekte abspielen, hat Abfrage für Eingabe (Keyboard und sogar Joystick!), hat Engine für Bearbeitung von Figurensteuerung inkl. Kollisionsabfrage...

Was derzeit noch fehlt, ist eigentlich nur ein vernünftiger Editor - weil ich ehrlich gesagt WIRKLICH KEINE LUST habe, Levels inklusive Figurenspawnpunkte so wie anno 1989 als Hexdaten einzuhämmern. Und ich HABE sogar schon so einen Editor gebaut ("TheGame3"), der ist sogar grafisch, Tastatur- und Maus- gesteuert... nur liefert der mir den Kram im "falschen" Format. Und der Code ist schon wieder 3 Jahre alt.

Wenn man dann endlich an einem Spiel werkelt, will man auch mal etwas kreativ tätig werden. - Das geht nicht, bzw. schwerer, wenn man dann wieder anfangen muß, mit den technischen Dingen herumzukämpfen.

Mein Problem ist, daß ich mich mit so GUI-Anwendungen ziemlich schwer tue - ich habe für sowas einfach kein Talent. Kommandozeilen-Tools, die intern coole Sachen machen - kein Problem! Engines, die intern großartige Dinge tun - mach ich andauernd! Auch Dinge, die eine GUI so BRAUCHT, wie (grafische) Textausgabe oder sonstige grafische Elemente - alles schon gemacht, alles da, geht. Aber einen GUI-Editor zu bauen, der einigermaßen logisch aufgebaut und gescheit bedienbar ist: Da quäl ich mich dran rum. Es muß wohl an meiner allgemeinen Ablehnung von GUIs und GUI-basiertem Zeug liegen (diesen ganzen Windows-Kram kann ich bis heute nicht ernstnehmen), daß es mir so schwerfällt, damit irgend etwas vernünftiges zu bauen...

Nur, bei einem Spiel bräuchte man doch schon so etwas - um dann mal schon beim Entwickeln zu sehen, wie es aussieht und zusammenpaßt, wo die Elemente stehen, damit es noch spielbar bleibt: Kann man über den Abgrund drüberspringen? Kommt man auf die Plattform hoch? Ist es zu schaffen, auf die Entfernung noch die Feinde zu eliminieren/den Geschossen auszuweichen? Ist genug Bonus da, um diese schwere Stelle zu meistern? Ist nicht zuviel Bonus da, damit es nicht zu leicht wird? Ist die Balance von Anstrengung zu Erfolgserlebnis noch OK? - Klingt alles simpel, aber das kann selbst der abgefuckteste Freak wohl besser bewerten, wenn er beim Entwickeln das Level sieht und dran eingreifen kann als wenn er nur einen Block von Hexwerten sieht.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ja, wie gesagt: Natürlich alles schöne Ideen - und auch wert, darüber nachzudenken. Allerdings vielleicht eine gute Idee, erst einmal ein spielbares kleines Teil zu bauen, um dann zu sehen, ob diese zusätzlichen Features dann noch möglich sind.
Es hält sich schon länger eine gewisse Spielidee in meinem Kopf, eher Arcade, nicht jetzt dieses Sound-Memory von dem ich mal geschrieben habe. Trotzdem werde ich da mit gemütlichen ca. 20 fps hinkommen. Ich habe lange nichts mehr gemacht und bin wohl etwas eingerostet, werde wohl als erstmal mal eine Art Level machen in dem man einfach etwas rumhüpfen darf, vielleicht ein paar Gegner noch die einfach rumlaufen. Das ganze dann mit Sound, dann fehlen nur noch die komplexeren Abfragen. Vielleicht mache ich zwischendrin auch schnell noch einen "Benchmark" der mir zeigt, wieviel fps ich mit Hintergrund komplett in den Puffer kopieren und dann den Puffer in die VGA kopieren hätte. Wenn der dann in Dosbox mit 20000 Cycles 70 fps bzw. mehr schafft kann mir das bzgl. der Blocktechnik zu denken geben, ansonsten sieht es ja positiv aus.
Ja, wie gesagt: An sich auch meine Meinung - am besten sieht man es, wenn man mal ein (wenn auch kleines) Spiel macht, um zu sehen, wie alles zusammenpaßt und performt. Da habe ich heute ganz unten auch wieder etwas angehängt (siehe später).
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ja, entschuldige meine harten Worte. Ich werde da in letzter Zeit immer pragmatischer.
Da muss ich wohl noch hinkommen. Mir machen Spielereien beim Programmieren immer noch mehr Spaß als das Spielen von Spielen, das ist vielleicht das Problem.
Naja, mir geht es ja ähnlich. Ich "fummle auch gerne herum". Nur geht es mir dabei eher weniger darum "den komplexesten Algorithmus" gefunden zu haben oder so - ich freue mich immer, wenn ich bei meinem Zeug Speicher sparen UND Geschwindigkeit rausholen konnte.

Bei mir ist es:
Speichersparen ja - aber nicht um jeden Preis. (Heißt: Wenn dafür alles unangenehm langsam wird, ist es das nicht wert.)
Geschwindigkeit ja - aber nicht um jeden Preis. (Heißt: Wenn ich dafür so riesige ungepackte Daten brauche, daß ich kaum etwas Sinnvolles machen kann, bevor Speichergrenze erreicht, ist es das nicht wert.)

Deshalb versuche ich ja oft, hier einen guten Mittelweg zu finden. In Einzelfällen - bei TOOLS - kann es schonmal vorkommen, daß ich ich das eine oder andere Extrem nehme:
Z.B. mal viel Speicher brauche, Geschwindigkeit aber keine Rolle spielt, dann können es auch mal komplizierte Sachen sein, Hauptsache ich kriege die Daten irgendwie unter.
Oder eben, daß etwas schnell gehen soll, weil z.B. das Tool etliche Male aufgerufen werden soll und zehntausende von Files damit bearbeitet werden sollen: Wenn dann genug Speicher da ist, dann werden auch mal riesige vorberechnete (oder nur einmalig berechnete) Tabellen / Bezüge / Bäume benutzt, damit der Kram auch irgendwann mal fertig wird.

Bei SPIELEN (im Gegensatz zu TOOLS) habe ich das aber bisher noch nie erlebt, daß es mich nicht stört, wenn es langsam wird. Manchmal/oft kriege ich es nicht besser hin - aber es stört mich dann trotzdem. Daß die erreichte Geschwindigkeit bei einem Spiel mir nicht ausreicht, passiert mir auch wesentlich häufiger, als daß mein Speicher nicht reicht. Bei Speichernutzung habe ich mir schon lange gewisse Sachen angeeignet, die "Nutzung" von "Verschwendung" trennt - wenn ich hier an Grenzen komme, habe ich auch oft einen Weg gefunden. Bei Spielgeschwindigkeit jedoch stoße ich an Grenzen, unter die ich irgendwann nicht mehr komme - was aber auch daran liegt, daß ich meine Routinen jetzt immer benutze und die nunmal so sind wie sie sind...

Jedes Feature, das zusätzlich in der "Spielschleife" drin ist, macht diese unweigerlich langsamer. Ich habe selten erlebt, daß ich durch HINZUFÜGEN von Code etwas schneller bekomme - eigentlich immer eher vom WEGLASSEN. Manchmal will man aber ein bestimmtes Feature haben - aber dann muß man eben mit dem zusätzlich benötigten Speicher oder dem zusätzlichen "Frame weniger" leben können.
zatzen hat geschrieben:So ziemlich alle meine "Entwicklungen" der letzten Jahre haben keine echte Rechtfertigung, aber sie haben mich motiviert, zu programmieren, und dabei viel zu lernen, das ist dann wiederum der Nutzen.
Da geht's mir ähnlich. Wenn der subjektive Nutzen ist, etwas zu lernen (um z.B. ein besser Coder zu werden), ist schon ein Nutzen erreicht.
zatzen hat geschrieben:Ich würde es so beschreiben, dass am Ende schon ein Spiel rauskommt, was aber ohne meine Spielereien kurz gesagt besser wäre (performancemäßig vor allem) - aber: Ich bin derjenige der das Spiel machen wird, und der motiviert sich eben aus seinen krummen "Erfindungen" heraus. Kein anderer wird dieses Spiel machen, und ohne meine komischen Formate und Routinen würde es nicht dazu kommen. Ein wenig werden diese Dinge aber auch ins Spiel durchdringen: Ich werde Trackermusik (ZSM) drin haben und Sample-Soundeffekte, und bei der Grafik werde ich nicht kleckern. Allemal werde ich eben keine 70 fps haben, man wird behaupten können es sei ruckelig - ich erwähne aber an dieser Stelle einmal wieder den alten bekannten "Captain Comic", ein Spiel das auch mit 10 fps funktioniert hat und gespielt wurde.
Ja, dann hatte ich wohl den Anreiz mit dem 8x8-Block-basierten Ein-Bild-Spiel (ohne Scrollen) nicht ganz verstanden. Ich hatte irgendwie angenommen, daß es darum ginge, die Framerate zu erhöhen, um ein flüssigeres Spiel zu haben und weniger daran gedacht, daß die gewonnene Rechenzeit dazu da sein soll, komplizierte Zusatzdinge zu machen, für die diese freigewonnene Rechenzeit zum Verbrauch dient - und es dabei dann nicht schlimm wäre, wenn es danach genauso bremsen würde wie mit Vollbild-Neupixeln ohne die Zusatzdinge. - Also ein Mißverständnis, kann ja mal passieren.
zatzen hat geschrieben:Ich kann mir vorstellen dass Dir das alles gegen die Hutschnur geht so wie ich da rangehe, denn es ist ja irgendwie auf DOS-Ebene durch meinen ganzen Firlefanz irgendwie vergleichbar damit, was heutzutage so in der Windows-Welt los ist, immer langsamer etc., nur dass bei mir der Faktor immer größer nicht dazukommt sondern eher immer kleiner, auf Kosten der Performance.
Ach, meiner Hutschnur geht's gut. Wer bin ich denn, anderen Leuten irgendwas vorschreiben zu wollen? Wie ich ja bereits sagte: Xpyderz - mein einziges Spiel, das auch in dieser Sparte (grafisch, interaktiv, komplex) "mitreden" kann, erreicht bei mir ebenfalls keine volle Framerate. (50 FPS wäre in Ordnung, weil das der Spielstep ist. 70 FPS wäre dann die Monitorrate.)

Auf meinem 486 X5, 133 MHz erreicht Xpyderz am Spielstart, wenn noch wenige Figuren da sind, ca. 42 FPS, später, bei mehr Figuren, gehen die dann noch runter. Gut, dieses ganze Kollisionszeug ist ja auch nicht gerade ohne, und außerdem ist der ASM-Anteil da noch weitaus geringer als bei dem Zeug, das ich aktuell so mache. Also sollte ich vielleicht nicht immer so über Performance abledern. Andererseits finde ich schon, daß 30-40 FPS für so ein Spiel trotzdem noch eine andere Liga sind als z.B. 10.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Da gibt es diese Episode von so ehemaligen Microsoft-Mitarbeitern (und auch bei anderen Softwarefirmen) : So "Chefs" und "Manager" sind ja fachlich oft blöde und verstehen nicht wirklich, was ihre Angestellten da eigentlich machen.
Davon wird auch in dem Vortrag in dem Video erzählt. Die Chefs haben keine Ahnung von Softwareentwicklung, deswegen ist es wichtig dass die Programmierer selbstverantwortlich und diszipliniert sind.
Ich lese gerade "VolksComputer" (englisches Original heißt "On the Edge") - da geht's um Commodore und wie die erst gestiegen, dann gefallen sind... und viel Hintergrundzeug von den Leuten, die damals dabei waren.

Da sieht man auch, wie die Hardwareingenieure UND Softwareentwickler schon damals gegen so strunzdumme Manager und Marketingleute (die von der Materie kaum 10% Ahnung hatten) kämpfen mußten, damit trotzdem noch etwas vernünftiges dabei rauskommt. Gerade bei Commodore hat diese Riege von Leuten die ganze Zeit mehr gestört als geholfen und tragen die Hauptschuld an dem meisten Mist, der da passiert ist. Ohne die Egozentrik und Borniertheit bestimmter "Entscheidungsträger" würden viele Firmen wesentlich besser laufen. Manchmal denk ich, so Leute werden in Firmen ab bestimmter Größe nur eingestellt, weil man denkt "das muß so sein" oder "weil das alle so machen". Der Beitrag, den da manche von leisten, ist irgendwie oft schwer erkennbar/definierbar.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Irgendwie bin ich nicht gut darin, große Sourcen fremder Leute zu lesen. Das mag auch daran liegen, daß ich mir quasi das allermeiste eher selbst beigebracht habe und daher meinen eigenen eher schrägen Programmierstil gefunden habe. Was (nicht nur) das angeht, bin ich sowas von unprofessionell...
Ich finde mich meistens auch schlecht in anderen Sourcen zurecht, einfach schon weil die einen anderen Stil haben. Deswegen habe ich mich auch in Deinen Democode hier noch nicht so reingekniet, aber er ist natürlich nachvollziehbar.
Ja, ich habe schon versucht, mir etwas Mühe zu geben... aber ich dokumentiere viel zu wenig. Meine Doku passiert meistens extern in irgendwelchen Beschreibungsfiles - habe oft keine Lust, den Sourcecode damit vollzuballern. Ich selbst brauche bei vielen eigenen Dingen schon keine Doku mehr, weil ich beim Code schon "an der Form" erkenne, was der macht, wenn es etwas ist, was ich immer wieder so mache.
zatzen hat geschrieben:Mir helfen aber solche Dinge wie Einrücken, und bisher habe ich Variablen z.B. immer nur mit einem VAR Statement definiert. Aber vor jede Variable ein VAR zu schreiben kann auch die Übersicht erhöhen...
Kommt bei mir drauf an - mal so, mal so. Bei mir liegt's oft daran, daß ich definierte und undefinierte Variablen mitunter "mixe" und die definierten muß man mit CONST beginnen.
Also CONST B:byte=5; ist eigentlich eine (veränderliche!) Variable B, nur daß sie gleich mit 5 initialisiert ist. Aber da muß ich danach eben wieder VAR benutzen, wenn noch weitere undefinierte kommen. Und weil ich da manchmal zwischendurch noch welche einfüge, bin ich "sicherer", wenn da überall VAR steht - sonst fehlt es beim Compilieren und der meckert mich an.

Unterschied: CONST B=5; macht eine unveränderliche Konstante B. CONST B:byte=5; macht eine Byte-Variable B, die auf 5 gesetzt ist, aber auch geändert werden kann. Früher wußte ich das nicht, hab immer diese VAR gemacht und dann am Anfang des Programms nochmals extra alle Variablen auf gescheite Default-Werte initialisiert. Mal abgesehen davon, daß das mehr Mühe beim Eingeben macht, erzeugt es auch mehr Code...
zatzen hat geschrieben:Programmieren als Beruf könnte ich auch schlecht. Es ist nur schön und praktisch grundlegende Dinge zu können um sich hin und wieder Tools zu bauen, oder doch mal irgendwann ein Spiel.
Ja, Tools baue ich oft so "nebenbei", wenn ich mal eins brauche. Habe gerade gestern wieder 3 Tools gebaut:
Eins, das zum Versionen erhöhen ist (liest die Sourcefiles, speichert sie - und andere genannte - in einem Verzeichnis mit alter Versionsnummer und erhöht die Versionsnummer in den aktuellen Sourcen. Das hab ich bisher manuell gemacht. Aber wozu, wenn man 'n Computer hat?
Ein anderes, das nicht ein File löscht, sondern alles AUßER dem angegebenen File (oder mehreren) in einem Verzeichnis. Wenn man Files in einem Verzeichnis packt (RAR), kann man blöderweise beim Batch-Aufruf von RAR nicht die Option nutzen, die die Oberfläche hat - nämlich: Nach dem Packen alle löschen.
Und ein drittes, mit dem ich einen Filenamem ändern kann (bei gleichbleibender Erweiterung) auf den Namen des Verzeichnisses, in dem es liegt. Das hat nur etwas damit zu tun, wie ich mein Zeug archiviere, daher brauchte ich das.
Das erste der Tools war noch etwas umfangreicher, dafür habe ich vielleicht eine Stunde gebraucht. Die anderen beiden jeweils nicht mal 10 Minuten. (Liegt daran, weil ich mir ja schon so viele nützliche Units gebaut habe. Und natürlich daran, daß ich einige Erfahrung habe und schnell tippen kann.)
zatzen hat geschrieben:Ja... Ich finde es angenehmer mit dem Können weiterzumachen, was eben schon da ist, und in kleinen Schritten weiterzulernen. Daher sind mir gewisse Dinge noch eine Hürde, gleichzeitig weiss ich aber dass sich eben mittlerweile ein Spiel von mir schon sehen lassen könnte. Das einzige was sein wird, ist dass es wahrscheinlich nur auf 486ern gut laufen wird, obwohl jemand mit mehr Ahnung (oder Vernunft/Disziplin) das auch für den lahmsten 386er hinkriegen würde.
Naja, ich sage mal: Wenn ich meine Arcade01-Unit mal so erweitern würde, daß auch "einfache" Sprites (siehe ganz oben) damit gehen oder mal die Levelroutine etwas aufbessern würde, wäre sicher auch für 386er bei meinem Zeug noch Leistung dabei. Momentan - so wie ich es gelesen habe - ist z.B. Xpyderz auf 386er sehr langsam - selbst bei geringer Auflösung des Levels (160x200).
zatzen hat geschrieben:Ich denke dass ich jetzt erstmal noch mein "Ich kann transparente Grafik, Sample-Sound und überhaupt Assembler"-Abi feiern muss, indem ich damit mal was umfangreicheres anfange. Dann kann Mode-X kommen.
Ja, immer mal auch zwischendurch ein Erfolgserlebnis zu haben, um mit dem, was man schon hat/kann, etwas Tolles anzustellen, hebt auch ziemlich die Moral und ist ein Ansporn, weiterzumachen. Immer nur "Rumentwickeln ohne Erfolgserlebnis" wird irgendwann zur Bürde - und das sollte ein Hobby nicht sein.
zatzen hat geschrieben:
DOSferatu hat geschrieben:[Video]Naja, ich guck's mir bei Gelegenheit mal an.
Ja, ich dachte da nur an Dich, weil da auch bemängelt wird dass heute schlunzig programmiert wird, und auch mehr oder weniger erklärt wird, warum.
Naja, auch ohne es bisher gesehen zu haben, kann ich mir denken warum: Es geht um Geld. Im sogenannten professionellen Bereich geht es ja nicht nur um Speicher und Performance, sondern auch darum, wie schnell ein Projekt fertig wird. Und daß man mit vorgefertigten Bibliotheken und portierbaren Skriptsprachen schneller fertig wird als wenn man sich in die Assembler-Grüfte begibt, steht wohl außer Frage.

Und daß heutzutage auch nur noch DIESER Schlunz gelehrt wird, liegt daran, weil sich diese Leute (Studenten) ja darauf vorbereiten wollen, im o.g. professionellen Bereich zu arbeiten. Mein Kumpel baut mit so kleinen Embedded-Systemen rum, mit kleinen Chips - EPROM und RAM im kB-Bereich. So etwas könnte man keinem heutigen "Diplom-Programmierer" mehr geben - wenn schon das grundlegende Framework, was der "unbedingt braucht" (damit er überhaupt damit arbeitet) schon 10 MB groß ist...

Auch gleich mal zum zweiten Teil:
zatzen hat geschrieben:So, "Benchmark" mal grob gemacht. Ich hatte jetzt keine Lust das genau zu machen und mir den Timer zu erschliessen, sondern hab einfach mal 1000x 64000 Byte von einem Speicherbereich in einen anderen kopiert und den dann nach A000. Gibt hier ca. 300 Durchläufe pro Sekunde d.h. die Schleife dauert etwa drei Sekunden. Da könnte ich jetzt die Flinte ins Korn werfen und sagen das bringt alles nichts mit den 70 fps bei 700 Blöcken. Es zeigt sich aber auch dass alles, wie zu erwarten, entsprechend schneller läuft wenn man den Umsatz reduziert, bei 16000 Bytes (buffer2buffer2vga) * 1000 dauert die Geschichte nur noch ca. eine Sekunde. Ich weiss dass DosBox kein guter Test dafür ist, aber was anderes hab ich gerade nicht.
Ja, wie gesagt: Damit kann man eigentlich nicht realistische Benchmarks machen - DOSbox versucht ja alles so schnell wie möglich abzuarbeiten und haut am Ende nur eine "Kunst-Bremse" rein. Das ist für Benchmarks eher ungeeignet.
zatzen hat geschrieben:Du hast in Deinem Demo-Code die ganzen Blockmarkierungen in reinem Pascal mit Schleifen und IF umgesetzt, und es sind Routinen mit Stackframe (bzw. intern deklarierten Variablen). Du sagtest selber dass man da noch optimieren kann wenn man es in Assembler umsetzt, möglicherweise wird es dann schnell genug, dass sich die Sache wirklich lohnt.
Nicht nur "möglicherweise", sondern 100% sicher. Wie ja erklärt, sollte das nur als "Rahmen" dienen, um die generelle Funktionsweise mal abzubilden. Natürlich wäre ich, wenn's mir auf Speed ankommt, nicht so irre, zweidimensionale Pascal-Arrays und sowas zu benutzen.
zatzen hat geschrieben:Ich hatte jetzt erst gedacht, Du rufst für jeden einzelnen Block eine Routine auf, aber das ist ja nicht so. Ausser bei RecoverBlock. Und BlockToVGA. Also doch... Da sehe ich noch Optimierungmöglichkeiten, so eine Pascal-Routine hat ja durchaus einen gewissen Overhead und es dürften dann bei 700 Blöcken schon einige (wohl zweistellig-)tausend vermeidbare Takte zusammenkommen.
So ist es. Wie gesagt, das würde ich auch nicht so machen, wenn alles in ASM wäre. Nur wenn ich das in 100% ASM gebaut hätte (und wie Du ja am Beispiel schon siehst, schreibe ich NICHT jeden ASM-Opcode auf eine Zeile) und so wie mein ASM-Code normalerweise aussieht... Da weiß ich nicht, ob's da noch irgendwas gebracht hätte, Dir DIESEN Source zu geben...
zatzen hat geschrieben:Also danke für die Übersichtlichkeit durch die Strukturierung in freundlichem Pascal und Gliederung in Routinen, ich hatte nur von Anfang an gedacht, z.B. den ganzen Block2VGA-Schmonzius von einer einzigen evtl. teilweise entrollten ASM Routine erledigen zu lassen.
So würde man's ja auch normalerweise machen. Allerdings kommt ja hier dazu, daß man hier "Sprungakrobatik" zu veranstalten hätte - ob man will oder nicht. Grund ist ja, daß nicht JEDER Block zu kopieren ist, sondern manche ja und manche nicht (denn das ist es ja, womit wir beim "Ein-Screen-Spiel" die Hauptperformance rausholen wollen: daß nicht alles kopiert wird). Für solche Dinge habe ich auch einen schicken Ansatz, den ich öfter mal benutze, nämlich, daß ich zwar springe, aber nur ENTWEDER um etwas zu überspringen ODER um die Schleife zu wiederholen. Dazu mach ich die Schleifenabbruchbedingung doppelt und springe beim ersten Mal mitten in die Schleife rein:

Code: Alles auswählen

Spring nach A:
---- schleifenanfang----------------------------------------------
B: TUE DAS,WAS GETAN WERDEN MUß
   Teste Schleifenende, ja, dann springe nach Z:
A: Teste, ob es getan werden muß, ja, dann springe nach B:
   Teste Schleifenende, nein, dann springe zurück nach A:
---- schleifenende------------------------------------------------
Z: weiter
Das "Teste Schleifenende" ist bei mir gleich so "Erhöhen/Vermindern eines Index, der auch gleich ein Flag setzt". Also z.B. benutze ich dann nicht INC, sondern ADD. Klingt erstmal nach blöder Idee. Aber erstens: Wenn abgefragte "Tabellen" aus Words (oder höher) statt Bytes bestehen, muß man sowieso ADD nehmen. Zweitens: Das Low-Word eines 32bit-Registers kann man dann auf IRGENDEINEN Startwert setzen und das High-Word auf die Anzahl Durchläufe-1. Wenn man dann z.B. zum Register jedesmal $FFFF0002 addiert, dann ist das CARRY immer AN, außer wenn die Anzahl Durchläufe erreicht ist und um wieviel man den Low-Wert erhöht (ob 1, 2, 5 oder 42) ist egal. So kann man also ESI, EDI, EBX oder EBP benutzen und SI, DI, BX oder BP ist der Index. Und die Erhöhung des Index "testet" gleichzeitig auf die Abbruchbedingung. D.h. man springt hier "unten" so lange, wie CARRY=1 ist (JC) und "oben" nur dann wenn CARRY=0 (JNC). Gesprungen werden muß sowieso, weil ja auch "ÜBER"-sprungen werden muß - da kann man "unrollen" wie man will. Indem ich das aber so wie oben mache, ist es dann IMMER nur 1 Sprung. Gerade wenn die "TUE DAS,WAS GETAN WERDEN MUß" seltener vorkommt - was ja das Ziel ist - kann das helfen.
Wenn sie selten genug vorkommt, kann man sogar noch den Schritt weitergehen, und mit raus- und zurückspringen arbeiten - aber da muß man dann, wenn man "unrollen" will, auch entsprechend das Rücksprungziel anpassen ODER z.B. für 8x unrolled dann auch 8 so Routinen haben, mit jeweils anderem Ziel...

Ob und wie ich so optimiere wie obengenannt - oder auch noch andere Methoden, wo ich GARNICHT springe, sondern Dinge per Berechnung statt Sprung löse - mache ich immer vom jeweiligen Fall abhängig.

Ich liebe z.B. das Ding, das mir mal eingefallen ist, um nach einer Subtraktion von zwei Werten den absoluten Betrag des Werts zu bilden, also egal ob er positiv oder negativ ist, immer den positiven. Dazu brauch ich ein zusätzliches Register (ich nehm als Beispiel mal BX) und in AX steht z.B. das Ergebnis. CARRY ist entsprechend gesetzt oder gelöscht, je nachdem, ob die Subtraktion n Unterlauf hatte:
SBB BX,BX {damit wird BX=0, wenn Carry=0 und BX=$FFFF, wenn Carry=1}
ADD AX,BX {damit subtrahiere ich 0 oder 1, weil $FFFF ist ja wie -1, und 0 bleibt 0}
XOR AX,BX {und dann die Bits entweder umkehren oder nicht}

alternativ:
SBB BX,BX {damit wird BX=0, wenn Carry=0 und BX=$FFFF, wenn Carry=1}
XOR AX,BX {Bits vorher entweder umkehren oder nicht}
SUB AX,BX {damit addiere ich 0 oder 1, weil $FFFF ist ja wie -1, aber "minus minus 1" = "plus 1"}

Das ist beides die mathematische Variante, wie man das 2er-Komplement erzeugt (quasi eine Zahl negiert). Bei der ersten Variante ist danach Carry=0 (weil XOR das macht), bei der zweiten Variante ist Carry dann sogar wieder so wie es vorher war.

Die normale Methode, auf die man käme, wäre ja:
JNC @DONT
NEG AX
@DONT:

Da brauchts kein extra Register und einen Befehl weniger, hat dafür aber einen Sprung - also Löschen von Prefetch Queue... Kommt dann drauf an, was besser ist. Meist kann ich das "mißbrauchte" Register hinterher dann wieder gut anwenden (z.B. um die ursprüngliche Situation mit 2 Befehlen wiederherzustellen). Wo braucht man sowas? Naja, wenn man z.B. eine Linie mit 2 Koordinaten hat und feststellen will, ob der Anstieg der Linie größer oder kleiner als 1 (=45°) ist, um zu entscheiden, welche Routine man nimmt...
zatzen hat geschrieben:Was man noch testen könnte wäre noch, die Demo so umzubauen dass sie "herkömmlich" einfach den kompletten Hintergrund in den Puffer kopiert zum restaurieren, dann kommen die Sprites, dann komplett rein in den VGA Speicher. Kann ich ja mal machen, und dann wird man sehen bis zu welchem Punkt, unter gleichen Bedingungen (ohne Sound etc.) welche Methode vorteilhafter ist. Bei 700 Blöcken wird einfaches kopieren schneller sein, und Vorteile von der Blocktechnik werden sich nur bei wenigen Cycles zeigen.
Ja, der Vorteil der Block-Variante ergibt sich selbstverständlich nur dann, wenn wesentlich weniger in Blocks kopiert werden soll als der ganze Screen. Das ist ja quasi der Sinn der ganzen Sache, wenn man ein "stehendes Bild" hat und nur auf Änderungen reagieren will: Daß diese natürlich so "gering" sind, daß man sich das "komplettkopieren" sparen kann. Man könnte auch bei jedem Frame mitzählen. wieviel zu kopieren ist, um dann beim nächsten Frame dynamisch anhand der vorigen Anzahl zu entscheiden, ob man das ganze Bild kopiert oder blockweise. Weil sich die Anzahl Sprites ja nicht von Frame zu Frame drastisch ändert, kann das helfen.
zatzen hat geschrieben:Hab jetzt einen kleinen Fehler(?) gefunden, die Routine UnMarkSpr wird gar nicht benutzt.
Ja, war nur zum Anfang als Test drin, wird nicht mehr gebraucht, weil ja dann später intelligenter gelöst.
zatzen hat geschrieben:So, ich habe jetzt mal das ganze Blockkonstrukt aus der Frame-Routine entfernt und durch einfaches Pointer2Pointer-Kopieren ersetzt. Es ist auf der ganzen Linie etwas schneller, bei 3000 Cycles bis 12 Sprites mit 70 fps, bei 20000 Cycles volle Fahrt bis 255 Sprites. Dass also die Blockmethode so abstinkt, kann dran liegen dass die Idee einfach nicht gut ist, oder eben an der Umsetzung, die mit IFs, Schleifen und Routinen mit Stackframe/Overhead(kläre mich gerne auf wie man das besser nennt, was ich meine ist, dass es keine simplen Routinen sind die nur ne handvoll Takte fressen wenn sie gerufen werden und sich verabschieden) einfach deutlich langsamer ist als wenn man das minimalistisch in Assembler umsetzt. Völlig klar, dass Du es mit Absicht exemplarisch in fluffigem Pascal gehalten hast, aber vielleicht verzerrt es etwas die Tatsachen wenn man genau das Ding als Untersuchungsobjekt nimmt.
Ja, wie bereits erwähnt: Es war nur eine Machbarkeits-Demo, um zu zeigen, ob das ganze Prinzip als solches überhaupt funktioniert, d.h. ob die Idee an sich das macht, was sie soll. Daß das mit Pascalschleifen und zweidimensionalen Arrays nicht so performt wie es könnte, war mir völlig klar. Die Block-Kopier-Routinen in 100% ASM zu schreiben, war nur, weil es dazu ja keiner Erklärung bedürfte, was die machen.
zatzen hat geschrieben:Eigentlich wollte ich mich noch über die Verwendung zweidimensionaler Arrays "beklagen", da ich damit die Erfahrung gemacht hatte dass Pascal dann zum Adressieren Orgien mit MUL kompiliert. Ich habe das hier mit einem 25x40 Array aber mal getestet und es kommt raus:[...]
Ja, wie gesagt: Überflüssig zu erwähnen. Ich kenne das Problem und weiß auch, was Hochsprachen da anstellen. Selbstverständlich keine gute Idee, sobald es an die REALE Umsetzung geht. Da wären (eindimensionale) "Offset-Tabellen" eine viel bessere Wahl. Also, nochmal: Die Benutzung zweidimensionaler Arrays war kein Vorschlag für Performance, sondern sollte nur verdeutlichen, was das macht.
zatzen hat geschrieben:Ich mach es mal in Assembler und dann sehen wir weiter.
Wenn es ein Schuss in den Ofen wird, wäre Mode-X konsequent.
Naja, Mode-X heilt auch nicht alles. Mode-X macht nur Sinn, wenn man immer den ganzen Screen neuzeichnen will und dann quasi sich das "zusätzliche Kopieren ins VGA" sparen will. Prinzipiell kann man wahrscheinlich sogar schneller sein, wenn man das Frame in einem 64000 Puffer erstellt, die Sprites draufklatscht und dann alles in VGA kopiert (16000 DWord-Zugriffe), als wenn man wie ich das Level und die Sprites direkt in VGA (Mode-X) Speicher schreibt, denn das sind ÜBER 64000 BYTE-Zugriffe auf VGA-RAM. 64000 fürs Level und dann die zusätzlichen für die Sprites, die jedesmal über das Level gepixelt werden. Bei sehr vielen Sprites sind das dann vielleicht schon 80000 oder 90000 Byte-Zugriffe auf VGA - und alles nur, um sich a) den 64000er Zusatzpuffer zu sparen und b) das Umkopieren auf VGA. Also ob "meine" (Double/Triple...) Buffering Methode in Mode-X wirklich die bessere/schnellere ist, würde ich nicht 100% unterschreiben.

OK, dann (fast) genug erstmal wieder von mir.
Nur, weil Du es mal erwähntest: Habe zwar auch die TGAME.ZIP da, die nur das "zusammengepackte" TGAME.EXE ist, aber habe jetzt mal das ganze Ding als Einzel-Komponenten hier, damit Du mal siehst, wie das aussieht:
http://www.imperial-games.de/z/testgame.zip

Da ist auch der "Assembler" drin, der den GameSys2-Source in den Bytecode für die GS2-VM "assembliert" und die Grafiken als PCX. Kann man prinzipiell ändern, wird dann trotzdem so ausgeführt. Da ist auch eine LIESMICH.TXT drin (mit "Windows"-Zeichensatz-Format). LIESMICH.DOS ist der gleiche Text in 437er Codepage. Die GAMESYS2.TXT ist praktisch die Referenz, die das ganze GameSys2 komplett erklärt - absichtlich etwas redundant (manche Dinge werden an mehreren Stellen erwähnt).
Alles andere kannst Du in der LIESMICH nachlesen. Kannst TESTGAME.EXE ja mal testen.
Achja: Das ist kein Spiel. Das ist ein Testprogramm, mit dem ich Zeug wie GameSys2, Arcade01 und diesen ganzen Kram teste. Deshalb Bedienung etwas fragwürdig und Performance keine Referenz dafür, wie es unter realen Bedingungen performen würde.

P.S.: Falls Du Bock hast - wovon ich kaum ausgehe - kannst Du Dir ja die TGAME.GS2 anschauen oder sogar damit rumspielen und sie ÄNDERN (Referenz ist wie gesagt GAMESYS2.TXT) und dann mit GS2ASM neu compilieren und TESTGAME - auch mit anderen Grafiken - starten...
So wie in TGAME.GS2 sieht die Figurensteuerung für die Figuren dann aus.

Re: Eigenes Videoformat

Verfasst: Mi 10. Jun 2020, 22:58
von zatzen
Zunächst einmal wieder ein kleines Disassemblat. Du wirst es wohl schon selber überprüft haben, bin mir aber nicht sicher, und wollte es selber einmal bestätigt haben. Es geht um PRED:

Code: Alles auswählen

Pascal-Code:
{var x, y: byte;}
y := pred(x);
y := x - 1;

Kompilat:
(pred)
0x0000000000000000:  A0 50 00    mov al, byte ptr [0x50]
0x0000000000000003:  FE C8       dec al
0x0000000000000005:  A2 51 00    mov byte ptr [0x51], al
(-1)
0x0000000000000008:  A0 50 00    mov al, byte ptr [0x50]
0x000000000000000b:  30 E4       xor ah, ah
0x000000000000000d:  48          dec ax
0x000000000000000e:  A2 51 00    mov byte ptr [0x51], al
PRED ist also wirklich schneller, und bei -1 wird von Word- bzw. Integer-Breite ausgegangen, wohl für den Fall dass man noch mehr anstellt als x - 1.
DOSferatu hat geschrieben:Ähm... - Dir ist aber schon aufgefallen, daß die entsprechende Routine zum Kopieren von Pointer nach Pointer schon DRIN ist, oder? Wie gesagt, das "Mosaik-Kopieren" habe ich nur zusätzlich gemacht.
Ja. Kleines Missverständnis. Meine Befürchtung war, dass wenn das Spiel für die nur teilweise Rekonstruktion des Hintergrunds und ebenso nur teilweises Updaten in den Grafikspeicher ausgelegt ist, es sein könnte, dass bei einem kompletten plötzlichen Bildwechseln es zu einem Hakeln kommen könnte. Also, wenn man doch ein Spiel mit wenigstens "Blättern" machen will, wie Cauldron II oder Prince of Persia. Oder Kotzman II. Daher dachte ich, wäre die Mosaik-Methode eine Möglichkeit, das ganze zeitlich über ein paar Frames zu verteilen bevor es im Spiel weitergeht. Zügiger natürlich, nicht 32 Blöcke pro Frame sondern z.B. 200. Mein Problem ist ja, dass es mir noch nicht eingegangen ist, den Sound quasi "im Hintergrund" für sich selbst laufen zu lassen. Ich sträube mich nicht gegen eine Alternative zum "Sound Timing", aber ich habe einfach keinen Schimmer, wie man die Berechnung der Sounddaten so koordiniert, dass diese gleichmäßig über die Leistung verteilt wird und nicht schubweise alles ausbremst.
Daher bleibt mir nur das Prinzip, alle Berechnungen nacheinander zu takten, und dann wird in der Hauptschleife eben z.B. zuerst die Steuerung/Kollisionen berechnet, die Grafik, dann der Sound, und in der "freien Zeit" oder wohl besser per Interrupt die Tastatur abgefragt. Ergebnis ist dann eben, dass die Framerate konstant ist und die Leistung nicht voll ausgenutzt wird, oder, die Leistung reicht nicht und es hakt und der Soundpuffer loopt. Im Grunde wie bei Deiner Demo, wo Du auf Retrace wartest, dort halbiert sich ja die Framerate auch direkt wenn es zu viele Sprites/Blöcke werden. Unnötig zu erwähnen, dass Du in richtigen Spielen die Sache anders angehst. Aber vielleicht komme ich ja mit meiner Sache durch. Ich habe in Dosbox 20000 Cycles eingestellt und bisher laufen meine Sachen damit ohne Hakeln. Dein TestGame hat bei dieser Leistung gerade mal ca. 5 fps. Unten dazu mehr und vorab: Keine Wertung, nur Feedback. Ich habe solche umfangreichen Dinge noch nie programmiert und kann mir kein Urteil erlauben.
DOSferatu hat geschrieben:Die Routine heißt CopyPtr2Ptr. Und die benutzt selbstredend natürlich die bekannte Variante, 16000 DWords mit REP;MOVSD zu kopieren.
Ja, und genau die habe ich verwendet für die Modifikation des Codes um zu testen wie es performt wenn man einfach den kompletten Hintergrund restauriert und alles komplett in den Grafikspeicher kopiert.
DOSferatu hat geschrieben:Alles was in einem boolean nicht 0 ist, wird als WAHR gewertet.
Ich war nur zwischendrin schonmal verunsichert quasi über eine offizielle Definition. Ich meine irgendwo (vielleicht Basic) TRUE als -1 (also $FFFF) gesehen zu haben, woanders aber meist mit 1, also nur ein Bit gesetzt...

Praktisch, mit den Booleans in Verbindung mit absolute... Das mit der Länge bei Strings hattest Du mir ja schon vor Längerem erzählt. Bei Pointern habe ich zuletzt, beim portieren des Beni-Players, immer Offset und Segment als Word nacheinander definiert und einen Pointer mit absolute auf die Offset-Variable deklariert, weil ich LDS bzw. LES einsparen wollte, braucht ja mehr Takte als wenn man die Register direkt aus Variablen speist. Siehst Du soetwas auch als sinnvoll an? Oder vielleicht sogar "gefährlich" weil evlt. wegen der Speicherauslegung zwei Words nicht genau nebeneinanderliegen?
DOSfertatu hat geschrieben:Wie gesagt: Pascal nur der Übersichtlichkeit wegen - damit man sieht, wann und womit ein Block aufgerüfen wird. Dir ist ja sicher aufgefallen, daß der Block selbst KEINE Multiplikation benutzt, um den Offset im Speicher zu berechnen, sondern mit ein paar einfachen Additionen/Shifts das Gleiche hinkriegt.
Ja, hab ich nachvollzogen. Da ist mir noch ein kleiner Überflüssigkeitsfehler aufgefallen, das erste "mov si, ax" kann weg.
Wenn ich das ganze später so gestalte dass alles in einer Routine gemacht wird, kann man wohl alles durch reine Addition adressieren. Und ich werde FS brauchen, weil man ja die Blockinfos liest, mit den zu lesenden Grafikdaten hantiert, und diese auch wohin kopieren muss.
DOSferatu hat geschrieben:Naja, die Sprites setzt man ja mit dem Verfahren nie direkt in die VGA, sondern in die "Zwischen-Ebene". Wie ich schonmal versucht habe zu erklären, hat das ganze hier 3 Ebenen:
Das Prinzip ist mir so schon klar, ich hab es mir ja ausgedacht...
Aber:
DOSferatu hat geschrieben:Es werden aber ständig alle Sprites-enthaltenen Blöcke kopiert - auch bei denen, die ihre Position nicht ändern. Das könnte man noch anpassen, aber:
Ja, das ist eine interessante Idee. Wäre auch wünschenswert wenn man z.B. eine Ecke eines Levels mit Boni zupflastert die sich nicht bewegen. Und die Umsetzung dessen wäre auch erstrebenswert, wenn es nicht wieder extra Performance kosten würde. Vielleicht könnte man eine Art Hardware-Sprite Emulation aufbauen, und von Grund auf nur alles relative bzw. was sich verändert überhaupt berücksichtigen, also auch beim Setzen von Sprites. Nur so eine Idee, wäre sicherlich alles kompliziert. Und mit Kanonen auf Spatzen geschossen wenn ich letztlich nur so ein 20x20 Pixel Sprite Spiel mit 20 Sprites habe.
DOSferatu hat geschrieben:und NATÜRLICH ist eine 16000-DWORD Kopierroutine (REP;MOVSD) schneller als 1000 Block-Aufrufe.
Auf jeden Fall bzgl. der Demo (völlig klar habe ich verstanden dass die schwächer performt weil sie illustrativ in Pascal gehalten ist). Und klar, dass 1000x BlockGefummel, selbst in bestem Assembler, langsamer ist als 64000 Byte per REP MOVSD. Der Datendurchsatz bei 20x20 Sprites und davon 20 wäre, über den Daumen gepeilt, was Restaurieren und in den Grafikspeicher kopieren angeht, vielleicht 20000 Byte. Komplett kopieren wären 128000 Byte, also das über 6-fache. Wenn ich da wenigstens nur um Faktor zwei bis drei gebremst bin durch das Block-Info-Gefrickel und den zerteilten Kopiervorgang statt REP MOVSD, dann würde sich das lohnen. Wenn nicht, könnte ich das verwerfen und über Scrolling nachdenken...
Dazu greife ich hier mal ein Zitat vor:
DOSferatu hat geschrieben:Naja, Mode-X heilt auch nicht alles. Mode-X macht nur Sinn, wenn man immer den ganzen Screen neuzeichnen will und dann quasi sich das "zusätzliche Kopieren ins VGA" sparen will. Prinzipiell kann man wahrscheinlich sogar schneller sein, wenn man das Frame in einem 64000 Puffer erstellt, die Sprites draufklatscht und dann alles in VGA kopiert (16000 DWord-Zugriffe), als wenn man wie ich das Level und die Sprites direkt in VGA (Mode-X) Speicher schreibt, denn das sind ÜBER 64000 BYTE-Zugriffe auf VGA-RAM.

Es wären ja schon mehr als 64000 Zugriffe wegen 320x240.
Nunja, auch durch unseren Exkurs vor ein paar Wochen in einem anderen Thread zum Thema Mode-X, erschien mir die Programmierung für MCGA mehr und mehr als regelrecht rückständig und veraltet. Und man will natürlich keine neuen Spiele schreiben wenn man dabei das Gefühl hat "es könnte alles zig mal besser performen und ich hab sooo viel mehr Speicher wenn ich Mode-X benutze" oder auch "wenn ich für MCGA programmiere ist das alles für die Katz und total amateurhaft".
Fakt ist einfach, mit MCGA kenne ich mich aus (ist ja auch kein Kunststück) und ich kann mich dann erstmal wirklich auf ein Spiel konzentrieren bevor ich mich monatelang mit Mode-X beschäftige und meine gefestigten Erfahrungen der letzten knapp 30 Jahre quasi über den Haufen werfen muss. Was bleibt, ist das Problem mit dem Sound-Timing, wobei ich durchaus sagen muss dass es auch Vorteile haben könnte wegen der viel von uns kontrovers diskutierten Synchronität von Bild und Ton - ich habe gerade nochmal ein Spiel gesehen wo diese nicht gegeben war und fand das ziemlich nervig, störend für das Spielerlebnis.
Ich kenne auch genug Spiele, bei denen die Framerate nie einbricht. Da wäre dann so gesehen die Implementation einer variablen Framerate nur überflüssiger Code und damit eine gewisse Verlangsamung. Anders ist das oft z.B. bei 3D Shootern, die fluktuiert die Framerate meistens sehr stark.
Eine mit 400 Hz getaktete Steuerung ist ein anderes Thema. Ich werde sehen wie ich die Steuerung und Tastaturabfrage integriere.

Zu Scrolling noch eine Bemerkung: "Blättern" hat auch seinen Reiz. Man sieht nicht direkt was einen im anderen Bereich erwartet, und innerhalb eines Screens ist alles viel aufgeräumter.
DOSferatu hat geschrieben:Ich meine damit das "Zeichne großes Bild auf 20 m²-Wand" Ding: Ab und zu, wenn ich mal eine gute Idee zu meinen habe und mich darin tage-/wochenlang verliere, gehe ich auch mal "einen Schritt zurück" (von der "Wand") und gucke mir das "Gesamtergebnis" an - ob es noch das ist, was ich eigentlich wollte...
Ja, ich habe ja auch noch keinen "Pinselstrich" getan. Bisher alles nur Überlegungen. Ganz am Anfang meiner "Karriere" hab ich schneller drauf los programmiert als nachgedacht. Das ist jetzt ziemlich umgekehrt, und solche Kompressionsüberlegungen sind natürlich nicht der Weisheit letzter Schluss. Kommt vielleicht einfach auch aus dem Gedanken des ollen ZVID(1) raus: Das lief mit all seinen ultrakomplizierten, eher schlecht programmierten Entpackvorgängen immer noch relativ performant. Und ich hab's in meiner ZSMPlay Moonwalker Demo verwendet als alleiniges Grafikmedium (auch der Boden, war ja ein ZSMPLAY-Modus, und da wollte ich Speicher sparen wo es nur geht, damit was frei bleibt fürs ZSM Modul). Kannst ja nochmal reinspinksen: https://youtu.be/uhuQgSfkg5M
Natürlich Framerate im Bereich von nur ca. 25 fps und nicht der ganze Bildschirm aktiv, und ZSM war zu dem Zeitpunkt noch nicht delta-komprimiert. Aber: Nur 20000 Cycles. Dürfte jeder noch so schlappe 486er oder sogar ein guter 386er packen.
Mittlerweise könnte ich mir vorstellen, ZSM eine 8K-Tabelle zur Beschleunigung der Lautstärkeeinrechnung zu spendieren, ich glaube das schlägt sogar mehr zu Buche als die Deltakompression im Schnitt. Ich hab so Chiptune-artiges genug bewundert und konzentriere mich lieber auf Spiel-relevante Sounddatenmengen im 100-200 K Bereich.
DOSferatu hat geschrieben:Naja, wie gesagt: Das "Hauptbild" könnte aus 1000 "Zeigern" bestehen, die auf die wirklichen Blöcke zeigen. Bei gleichen Blöcken ist der Zeiger gleich. Und da hier noch etwas "Luft" ist, hätte man zusätzlich noch Optionen, um gleich im "Zeiger" auch noch "Vorgaben" für verschiedene Arten von Packen zu haben
Ja, ich hatte auch spontan eine Offset-Tabelle angedacht, nichts anderes ist das ja was Du sagtest. Bei 10 Bit müsste man nur jedesmal shiften, oder auch wenn man es so macht mit FFxx jedesmal prüfen und und einen bedingten Sprung einbauen...
Aber geht ja nicht anders wenn man Alternativen zum einfach-64-Byte kopieren will. Bei Deinem Vorschlag mit dem oberen Byte > 249 spart man jedenfalls 1000 Byte, die ich sonst regelmäßig als Blocktyp-Info reingebracht hätte. Zum Glück ist CMP nicht so langsam wie es verschrien ist.
DOSferatu hat geschrieben:Außerdem ist es gar nicht mal so einfach, gerade mal 64 Bytes so zu packen, daß die "Meta-Daten" (Header, "Tabellen" usw.) am Ende so viel weniger belegen, daß es sich lohnt.
Bei ZVID2 (sorry falls ich Dich immer damit nerve) sind die Verhältnisse noch "enger", nur 16 Bytes, und hier ergeben sich nach bisherigen Tests mit GIF vergleichbare Ergebnisse. Schon allein weil man höchstens 4 Bit braucht für einen 4x4 Block und zusammen mit dem nur einem Block-Infobyte ein Komprimierungsverhältnis von 9:16 (56%) garantiert ist, mit Tendenz zu 7:16 (44%) und 5:16 (31%) oder eben 1 Bit, 3:16 (19%), geschweige von einfarbigen Blöcken, 1:16 (6%), die so gut wie immer auch vorkommen in Sprites, allein wegen transparenten Flächen. Also, je kleiner die Blöcke werden, desto wahrscheinlicher werden auch sehr niedrige Bitaufkommen, man muss dann nur die Blockheader auch klein halten, hier eben 1 Byte. ZVID2 hat natürlich noch einen kleinen Rattenschwanz Metapalette, der aber nicht Überhand nimmt, weil maximal 16 Farben in den Blöcken vorkommen, im Schnitt eher 4-8 Farben. Bei den Sprites Deines TESTGAMEs wäre die "Ausbeute" sehr hoch. Klar, dass das meiste nur demomäßig mit wenig Farben gepixelt ist und es in einem echten Spiel anders wäre, aber eben nur mal als Beispiel was wir beide nachvollziehen können. Wobei die Kugeln und Pilze durchaus "spielreif" aussehen, und ich bei den Figuren besonders das Hinhocken gut animiert finde. Und in der Demo ist es auch beeindruckend wie z.B. die Kugeln um die Figuren herumkreisen, soetwas könnte ich natürlich mit meinen Routinen nicht bzw. nur in "2D".

Für die Sache mit den Blockroutinen und für die Kompression der Hintergrunddaten hätte(!) ich angedacht, keine extra-Tabelle anzulegen, sondern die benötigten "Meta"-Farben direkt in den jeweiligen Blockdaten an die Bitdaten anzuhängen, und eben nur bis max. 4 Bit, darüber unkomprimiert. Angabe der Länge der "Palettchen" wäre überflüssig. Müsste man dann durch zwei-Register-Adressierung bewältigen können also z.B. [SI+BX], aber genauer habe ich mir darüber noch keine Gedanken gemacht, ich bin ja immer noch in der Brainstorming-Phase.
DOSferatu hat geschrieben:Und, was dazukommt ist: Sobald etwas byte-weise gepackt ist, verliert man den Geschwindigkeitsvorteil der 32-Bit-Zugriffe, d.h. die Anzahl auszuführender Befehle wird mindestens vervierfacht.
Das steht ausser Frage. Wenn man auf die ganze Blocktechnik pfeift könnte die Kompression des Hintergrunds lediglich eine alleinige Daseinsberechtigung haben, nämlich wenn man davon viele im Speicher halten will, diese redundant sind und großflächig aus wenigen Farben bestehen, und man schnellere Dekompression braucht als es ZVID(1) liefern kann. Soll nicht heissen dass ich sowas jetzt mache.
DOSferatu hat geschrieben:Beim PC kann man da leider auch nicht wirklich in eine bestimmte Richtung optimieren, weil man nicht weiß, was hier die Bremse ist, weil jeder PC anders ist - Beispiel: Bei PC mit schnellem VGA-Durchsatz aber langsamer CPU müßte man in die entgegengesetzte Richtung optimieren als bei PC mit langsamer VGA und schneller CPU. Wenn beides langsam ist, ist da wieder eine mittlere Speed-Optimierung am besten und wenn beides schnell ist, aber wenig Speicher da, optimiert man auf Speichersparen und nicht auf Geschwindigkeit...
Und das ist mir dann irgendwie zu hoch, und da erlaube ich mir lieber, zumal wir nicht mehr im Jahr 1990 leben, einen flotten 486er vorauszusetzen der dann etwas zaubern soll was auch ein 386er gekonnt hätte wenn ich nicht ich sondern ein besserer Coder dran gesessen hätte. Hatte ich ja quasi ähnlich schon im letzten Schrieb formuliert. 640K ist nebenbei schon irgendwie ne Einschränkung für mich und meine Ideen, deshalb eben die Formate mit Z vorne dran... Ich arbeite ein bisschen nach dem Prinzip, sich was einigermaßen vernünftiges technisches ausdenken mit der Absicht es in einem Spiel zu verwenden. Weniger, Spiel anfangen und dann "improvisieren". Das habe ich notgedrungen bei Kotzman II so gemacht und es war eher ärgerlich. Ich bin zwar ein paar Jahre jünger als Du aber auch irgendwie schon ein alter Sack, habe nicht mehr wirklich die Energie wie als 15jähriger, dafür aber mehr Geduld. Trotzdem stehe ich in meinem Wissen dort wo ich stehe, und nicht dort wo kommerzielle Spieleentwickler vor 27 Jahren schon standen und einen 386er-tauglichen 60fps Fullscreen 4-Wege Scroller stemmten (Lost Vikings z.B.). Sowas versuche ich lieber erstmal nicht. Eher mache ich erstmal da weiter, wovon ich seit der Fertigstellung von Kotzman II geträumt habe. Einziger Unterschied: Mit 17 hatte ich dann nen Pentium 200. Das wäre jetzt dann doch zu einfach. Und zu schlunzig, da bräuchte man ja fast kein Assembler, könnte man direkt was in Windows mit mehreren GHz anfangen.

Ein bisschen Off-Topic:
Ich hatte mal die Idee zu einer automatischen Spieluhr, wo also echte Instrumente gespielt werden könnten wie Glockenspiel und auch diverse Trömmelchen und sowas. Normalerweise würde man da heute wohl direkt mit MIDI rangehen, aber ich wollte es anders lösen: 32 NE567-Chips (das sind welche die auf bestimmte Frequenzen "einrasten" und dann sozusagen durchschalten wenn diese Frequenzen erkannt werden), als Steuertonträger wollte ich eine Audio-CD nehmen, mit auf dem linken und rechten Kanal jeweils bis zu 16 Töne gleichzeitig. Zum einen bin ich da so rangegangen weil ich keine Ahnung hatte wie man das alles per MIDI und dementsprechend Mikrokontroller löst - das mit den NE567 und den Steuertönen ist ja dann eher eine analoge Lösung. Zum anderen hätte ich es einfach lustig und interessant gefunden, sich zwischendurch auch mal die Steuertöne in einem normalen CD-Player anzuhören. Daran kannst Du sehen, dass es mir oft nicht immer nur ums Endergebnis geht, sondern dass ich auch Spaß an den Details habe, für die sich der "Endverbraucher" überhaupt nicht interessiert. Und dass diese komischen Herangehensweise für mich die Faszination ausmachen, obwohl man es auch effizienter machen könnte. War aber auch alles nur Brainstorming. Ich hatte bei den Überlegungen noch nicht für realistisch befunden dass ich die Ausdauer habe, die riesige Elektronik zu löten und die Kiste mit den ganzen Relais und Elektromagneten, Mechanik, so zu bauen dass das alles auch funktioniert.
DOSferatu hat geschrieben:Was derzeit noch fehlt, ist eigentlich nur ein vernünftiger Editor [...]
Ja, bei Kotzman II habe ich instinktiv direkt einen gemacht, mit dem man auch sofort spielen konnte, anders hätte ich die Levels nur schwerlich designen können.
DOSferatu hat geschrieben:Wenn man dann endlich an einem Spiel werkelt, will man auch mal etwas kreativ tätig werden. - Das geht nicht, bzw. schwerer, wenn man dann wieder anfangen muß, mit den technischen Dingen herumzukämpfen.
Ja, das geht dann nur bei mir eher in die Richtung, dass ich mir zuerst gewisse Formate ausdenke, die dann eine technische Basis für das Spiel dienen. Ich habe eben noch nicht so viel Erfahrung und denke nach dem Prinzip "erstmal lieber zusehen dass möglichst viel Daten möglich sind (komprimierte Formate) und wenn's klappt eben diese Beschleunigung durch die Blocktechnik. Für den Geschwindigkeitstest kann ich mir ja das Markieren von Sprites sparen und erstmal willkürlich Blöcke markieren, und dann einfach sehen bis zu welchem Grad teil-Block-kopieren einen Vorteil gegenüber 128000 Byte-Transfer hat.

Der Amiga war lange Zeit dem PC in einigen Dingen voraus, Spiele hatten am Amiga oft mehr "Multimedia" Features als der PC, nicht zuletzt beim Sound. Erstmal könnte man sagen, gut, für den Amiga waren 500K Standard, die hab ich ja beim PC auch. Man könnte also das Soundproblem so angehen und sehen dass man alles unkomprimiert in den RAM unterbringt, dann hat man vielleicht 16 Sekunden Sound in "Telefon"-Qualität drin (128000 Byte). Ich möchte da aber etwas mehr mit vergleichbarem Speicher und für Musik vielleicht wenigstens auch ein paar Samples mit 16 kHz. Deswegen das 4 Bit Delta bei ZSM. Umgehen könnte ich das Problem wenn ich für die Gravis Ultrasound programmiere, die hat standardmäßig immerhin 256K Speicher. Die ist selber aber kein Standard bzw. hat sich nicht so etabliert wie Soundblaster kompatible, und ich möchte da nicht so modular und mit Extrawürsten programmieren dass man die Wahl hat zwischen CPU-Berechnung und GUS, so dass es bei letzterer schnell laufen würde wie mit AdLib und bei ersterem total leistungshungrig. Wenn, dann würde ich mit Standards/Etablierten arbeiten und soetwas wie AdLib+One Shot DMA machen. Das wäre dann aber wiederum unterhalb Amiga-Niveau, gewissermaßen. Also: Um in die 640K einen "Amiga +" unterzubringen brauche ich etwas mehr Leistung, weil ja beim PC alles durch die CPU muss. Ähnlich wohl, wie es erst ab Pentiums auch flüssige Emulatoren für Amiga und Konsorten gab.
DOSferatu hat geschrieben:Mein Problem ist, daß ich mich mit so GUI-Anwendungen ziemlich schwer tue - ich habe für sowas einfach kein Talent.
Für einen Editor den man nur selbst bedient reicht ja eine Shortcut-Bedienung... Meine letzte umfangreichere GUI war die des Frankus Tracker II, da hat mir am wenigsten der Fileselector Spaß gemacht.
DOSferatu hat geschrieben:Deshalb versuche ich ja oft, hier einen guten Mittelweg zu finden. In Einzelfällen - bei TOOLS - kann es schonmal vorkommen, daß ich ich das eine oder andere Extrem nehme:
Z.B. mal viel Speicher brauche, Geschwindigkeit aber keine Rolle spielt, dann können es auch mal komplizierte Sachen sein, Hauptsache ich kriege die Daten irgendwie unter.
Eigentlich mache ich schon einen Mittelweg mit meinen Ideen, sonst würde ich noch "krasser abgehen". Ich wüsste allerdings gerade auch nicht wie. Ist vielleicht ein Glücksfall, dass meine Formätchen doch relativ schnell performen. Tools baue ich meistens in Freepascal, da nutze ich die 4 GB möglichen Speicher eher schamlos aus, nicht verschwenderisch (sondern dynamisch), aber es ist praktisch wenn man alle benötigten Daten erstmal komplett reinladen kann, in ein Output-Array bearbeiten kann, und dieses dann speichern. 2K gepuffere in DOS habe ich schon länger nicht mehr gemacht.
Die Zeiten haben sich geändert, in der Diskettenära war es wichtig, die Daten vor allem in den Dateien gepackt zu halten und man hat sie meist in den RAM entpackt weil man sie schnell brauchte. Ich habe 1998, drei Jahre nach der Fertigstellung von Kotzman II, das Spiel nochmal ein bisschen überarbeitet mit Assembler Prozeduren über CALL ABSOLUTE (Basic), mit denen ich statt um die 100 "luftige" Dateien zu laden alles RLE gepackt aus einer .DAT geladen habe. War mir irgendwie ein Bedürfnis.
Heutzutage ist alles aus einer Datei laden wohl selbstverständlich, Du hängst die Daten teils ja direkt an die EXE dran. Packen muss ich mit meinen Formaten nicht weiter, es ist im Speicher wie auf Datenträger schon so kompakt wie es in meiner Macht steht.
Evtl. wird das für Leveldaten noch anders sein. Für ein ein schönes Nostalgiefeeling wäre es toll, wenn mein angedachtes Spiel am Ende auf eine Diskette passen würde und man nichts installieren muss. Ein Diskettenlaufwerk hab ich mittlerweile zu meiner Schande gar nicht mehr, man kann ja heute noch froh sein wenn Computer überhaupt noch ein optisches Laufwerk haben.
DOSferatu hat geschrieben:Ich habe selten erlebt, daß ich durch HINZUFÜGEN von Code etwas schneller bekomme - eigentlich immer eher vom WEGLASSEN.
Das hört sich natürlich erstmal fatal an, wenn man über die Blocktechnik nachdenkt. Man tauscht zweimal REP MOVSD mit ein paar Befehlen davor durch zig Befehle mit Schleifen ein. Ich habe mal versucht, aus der ASM86FAQ die Takte von REP MOVSD herauszukriegen, ist mir auf die Schnelle nicht ganz klar, sind es auf einem 486er im Realmode 13 + 2n ? Dann wären es bei 128000 Byte rund 64000 Takte, wenn man das trotz des VGA-Schreibzugriffs so rechnen kann. Man könnte also geneigt sein zu sagen, so viele Takte Code gilt es zu unterbieten. Erscheint ja erstmal machbar. Auch wenn es nur 32000 sein sollten. Letztere Anzahl sollte man sich vielleicht als zu unterbietende "Hausnummer" setzen, wenn das ganze erfolgreich werden soll. Aber ich sehe schon dass das eng wird.
DOSferatu hat geschrieben:Ja, dann hatte ich wohl den Anreiz mit dem 8x8-Block-basierten Ein-Bild-Spiel (ohne Scrollen) nicht ganz verstanden. Ich hatte irgendwie angenommen, daß es darum ginge, die Framerate zu erhöhen, um ein flüssigeres Spiel zu haben[...]
An 70fps oder dergleichen kann ich nur denken, wenn ich ZSM fallen lasse, das Ding schafft in sich schon keine 70 fps, höchstens ohne nennenswerten Grafik/Text-Output. Vielleicht geht mit der Blocktechnik ein 70 oder 35 fps Ding mit AdLib. Commander Keen (die späteren, 4 etc.) hat 35 fps, nebenbei bemerkt. Sollte ja auch reichen. Mein olles Kotzman II läuft aber auf ca. 70 fps, wenn nicht gerade zu viel Feinde kommen. Da war ich meiner Zeit voraus, durch die sagenhafte Selbstlöschung der Sprites...
DOSferatu hat geschrieben:[...]dieses ganze Kollisionszeug ist ja auch nicht gerade ohne[...]
Ich habe mich mit soetwas nie so tief beschäftigt und das Problem "jeder mit jedem" und den immensen fakultativ bedingten Fällen noch nicht so gesehen. Ich glaube bei vielen Spielen sind die Feinde untereinander völlig unbetucht und kollidieren nur jeweils für sich entweder mit der Spielerfigur oder dessen Schuss. So habe ich es bei Kotzman II gelöst und es funktionierte. Also, 20 Feinde, ein Spieler, ein Schuss, 40 Kollisionsabfragen. Es ist lange her, aber ich meine ich hatte einfach für jeden Feindtyp ein Datenfeld und habe dann in Schleifen alles nötige abgefragt und berechnet bzw. Koordinaten verändert. Aber Du wirst einen Grund für die Kollisionsmatrix haben, den ich Mangels Erfahrung nicht sehe.
DOSferatu hat geschrieben:Ja, ich habe schon versucht, mir etwas Mühe zu geben... aber ich dokumentiere viel zu wenig.
Bei dem Democode geht das aber, der ist ja kurz und übersichtlich. Ich habe nochmal in meine ZSM Routinen reingeguckt, das muss ich erstmal wieder aufarbeiten bevor ich da guten Gewissens was verbessern kann.
DOSferatu hat geschrieben:Also CONST B:byte=5; ist eigentlich eine (veränderliche!) Variable B, nur daß sie gleich mit 5 initialisiert ist.
Wieder was sehr hilfreiches gelernt. Ich dachte bisher immer, Konstanten wären grundsätzlich nicht veränderlich. Code und Mühe sparen, sehr gut. Ich hab mir auch gerade noch eine Frage selbst beantwortet: Konstanten die man zur Array-Definition verwendet müssen fix sein.
DOSferatu hat geschrieben:Wenn man Files in einem Verzeichnis packt (RAR), kann man blöderweise beim Batch-Aufruf von RAR nicht die Option nutzen, die die Oberfläche hat - nämlich: Nach dem Packen alle löschen.
Genau sowas bringt Windows ausnahmsweise von Haus aus mit: Dateien markieren, packen, Markierung bleibt bestehen, Archivdatei ist nicht markiert. Dann Shift+Entf und der Haufen ist weg.
DOSferatu hat geschrieben:Und ein drittes, mit dem ich einen Filenamem ändern kann (bei gleichbleibender Erweiterung) auf den Namen des Verzeichnisses, in dem es liegt.
Könnte ich auch schonmal gebrauchen, aber für Windows eben. Mach ich vielleicht in Freepascal. Ja und Units sollte ich auch mal machen. Ich hab da so jemanden dem ich schonmal irgendwelche Tools und Konvertierer schreibe, wäre nur klug sich dafür mal ordentliche Units zum Lesen von Configs und dergleichen zu machen.
DOSferatu hat geschrieben:Ja, wie gesagt: Damit kann man eigentlich nicht realistische Benchmarks machen - DOSbox versucht ja alles so schnell wie möglich abzuarbeiten und haut am Ende nur eine "Kunst-Bremse" rein. Das ist für Benchmarks eher ungeeignet.
Ich habe nur den Eindruck, dass trotz allem Dein PC ungefähr 8x so schnell ist wie die DosBox hier mit 20000 Cycles die sich bei mir eingebürgert haben weil sie den Wirt nicht zu sehr stressen. Und mit denen läuft mein bisheriger Kram flüssig, mit Luft nach oben, das macht mir Hoffnung. Du könntest ja auch mal testen wieviel FPS ein Transfer 64000 Byte Buffer2Buffer2VGA bei Dir schafft, ist ja schnell geschrieben.
DOSferatu hat geschrieben:Für solche Dinge habe ich auch einen schicken Ansatz, den ich öfter mal benutze, nämlich, daß ich zwar springe, aber nur ENTWEDER um etwas zu überspringen ODER um die Schleife zu wiederholen.
Vielen Dank für die Erklärung dieser Methode. Das macht ja dann doch Hoffnung, die Block-Überprüfung kurz und knapp zu halten (angenommen man hätte pro Block 10 Takte, dann hätte man wohl schon fast verloren...). Wenn ich mich an's programmieren setze um das Blocksystem erstmal auf Geschwindigkeit zu testen (auch wenn das mit DosBox nur so larifari geht) werde ich das mit viel Sorgfalt umsetzen.
DOSferatu hat geschrieben:Gerade wenn die "TUE DAS,WAS GETAN WERDEN MUß" seltener vorkommt - was ja das Ziel ist - kann das helfen.
Wenn sie selten genug vorkommt, kann man sogar noch den Schritt weitergehen, und mit raus- und zurückspringen arbeiten - aber da muß man dann, wenn man "unrollen" will, auch entsprechend das Rücksprungziel anpassen ODER z.B. für 8x unrolled dann auch 8 so Routinen haben, mit jeweils anderem Ziel...
Ja, das muss ich im Zusammenhang dann sehen. Ich hatte angedacht, z.B. horizonal zu unrollen, also 40 Blöcke. Geschwindigkeitsmäßig dürften aber auch 8 Einheiten reichen, und da man das ganze linear und nicht zweidimensional angeht ist es ja egal wo man "trennt", es muss nur am Ende mit 1000 aufgehen.
DOSferatu hat geschrieben:XOR AX,BX {und dann die Bits entweder umkehren oder nicht}
Endlich mal ein XOR, NICHT um zu nullen. In die Verlegenheit würde ich auch gern mal kommen.
Ja, auf jeden Fall sehr gute Methode. Vielleicht fällt mir irgendwann was ein, wie man Werte begrenzen kann ohne Sprünge (also Pseudo: wenn x(signed 16 Bit) > 127 dann x = 127. Und auch das gleiche fürs negativ, bloß dann -128. Scheint mir aber aussichtslos. Ich glaube im Pentiumbefehlssatz gibt es etwas dafür.
Ich denk noch drüber nach. Vielleicht erstmal durch add 32768 in unsigned wandeln.
DOSferatu hat geschrieben:Man könnte auch bei jedem Frame mitzählen. wieviel zu kopieren ist, um dann beim nächsten Frame dynamisch anhand der vorigen Anzahl zu entscheiden, ob man das ganze Bild kopiert oder blockweise.
Ja, wäre ne Option, geht aber dann nur für das Kopieren in den VGA Speicher, der Hintergrund kann ja Redundanzkomprimiert vorliegen. Allerdings wäre es dann ja schon eine Ausnahmesituation, die so nicht vorgesehen ist. Bei variabler Framerate absolut sinnvoll, aber wenn ich das statisch auslege würde an dem Punkt wohl eher das Stottern anfangen, je nach Rechner.
DOSferatu hat geschrieben:Nur, weil Du es mal erwähntest: Habe zwar auch die TGAME.ZIP da, die nur das "zusammengepackte" TGAME.EXE ist, aber habe jetzt mal das ganze Ding als Einzel-Komponenten hier, damit Du mal siehst, wie das aussieht:
Schön, das wiederzusehen, ich hatte es schon gar nicht mehr so in Erinnerung. Vielleicht hast Du ja auch was geändert.
Ich habe jetzt mal 26800 Cycles eingestellt, das entspricht laut DosBoxWiki grob einem 486 mit 66 Mhz. Nicht super-flüssig aber äußerst "spielbar". CTRLBF zeigt 48, alles ein wenig verlangsamt durch die Textdarstellung, aber nicht wesentlich. Du merkst, ich bin gerade etwas Framerate-fokussiert, denn bei der ganzen Block-Geschichte geht es ja nur darum. Beim 2-Ebenen Modus wird es ziemlich langsam, CTRLBF zeigt dann 88. Ich bin überzeugt, dass Dein System großes Potenzial hat, ich bin nur irritiert wegen der vielen Vollbild-Scroller Spiele mit Bildschirmwiederholraten-FPS aus den Jahren um 1992/93 herum, die eben ihre volle Performance in der Dosbox bei lediglich 20000 Cycles erreichen... Ich kann erst "mitreden" wenn ich etwas spielbares vorzuweisen habe. Allemal könnte ich mich auf oben erwähnte ZSMPLAY Moonwalker Version stützen, die prima performt hat. Da war zwar nicht viel mit Kollisionsabfrage, aber immerhin konnte man so um die 50 Lemmings laufen lassen für die dann Bedingungen geprüft wurden.
DOSferatu hat geschrieben:Da ist auch der "Assembler" drin, der den GameSys2-Source in den Bytecode für die GS2-VM "assembliert" und die Grafiken als PCX.
Interessant und inspirierend, wie Du die Grafiken in dem PCX strukturierst. Naja, gucke mir gern solche Grafikbaustein-Kollektionen an. Man sieht gut, wie Animationen viel Platz belegen.
DOSferatu hat geschrieben:Falls Du Bock hast - wovon ich kaum ausgehe - kannst Du Dir ja die TGAME.GS2 anschauen
Reinsehen gerne, aber zum modifizieren müsste man sich erstmal reinknien und es verstehen.
Ich werde wohl das nächste Spiel erstmal noch hardcoden. Ich hatte zwar auch schon länger über so eine Game-Engine nachgedacht, diese wollte ich aber nicht durch Instruktionen wie eine VM realisieren, sondern vielmehr durch Definitionen die dann in Arrays landen und von fest programmiertem Code abgearbeitet werden. Vielleicht ein unüberlegter Ansatz. Ich sehe bei Deinem GS2 höchstens den Nachteil, dass es, wenn ich es richtig verstehe, so aussieht, dass die Instruktionen erst als Daten eingelesen werden müssen (kostet Takte fürs Lesen, hardgecodeter ASM würde ja direkt ausgeführt werden) und dann entsprechendes ausgeführt wird. Also gewissermaßen ein Interpreter. Kann mich aber irren. Du bist natürlich so sehr flexibel, da es ja regelrechte Programmierung und nicht nur Definition ist.
Ich habe vor 20 Jahren mal in den Dateien von Duke Nukem 3D gestöbert, und da waren Script-artige Definitionen dabei. Eher selbsterklärend mit Schlüsselwörtern wie "Spawn", z.B. wenn eine Figur zerstört wird und dann herumfliegende Körperteile "gespawnt" werden. Vielmehr Einblick in nicht 100%ig hardgecodete Sachen habe ich seitdem nicht. Daher dachte ich bei einer einer eigenen Game-Engine wie gesagt nach dem Prinzip, alles in Datenfeldern, und diese müssen nur je nachdem initialisiert werden und dann während des Spiels behandelt. Letztlich wird das nicht viel anders in meinem nächsten Spiel sein, da hat dann eben jedes Level seine Definitionen.

Wie auch immer: To do ist jetzt: 1. Block-Dingens Geschwindigkeits-Test programmieren, 2. ZVID2 Converter fertig machen und die Anzeigeroutinen dafür schreiben.

Re: Eigenes Videoformat

Verfasst: Fr 12. Jun 2020, 16:43
von zatzen
(Okay, letzte Änderung hier vorab: Ich habs erst nur "theoretisch" niederprogrammiert. Beim Testen ist mir dann noch aufgefallen dass da noch was drin fehlt weshalb ich das wahrscheinlich nochmal anders angehen muss. Geht hier also eigentlich nur um die Frage ob mov eax, ?:[?+?]; mov ?:[?+?], eax schneller ist als movsw und ob Kombinationen beim Adressieren langsamer sind als wenn man nur ein Register drin stehen hat. Aber ich glaub ich weiss es selber schon. Musst hierauf nicht ausführlich antworten, ich lass es aber mal stehen...)

Zwischenstatus:

Ich habe schonmal angefangen. Konkret werde ich das alles noch anpassen, aber ich habe schonmal ein paar Dinge zu erwähnen. Ich beziehe mich im folgenden auf die Taktangaben für 486er laut ASM86FAQ.TXT:

MOVSD scheint mit 7 Takten ziemlich langsam. Stattdessen verwende ich jeweils "MOV EAX, DS:[SI]; MOV ES:[DI], EAX", was abgesehen von eventuellen Read/Write Penalties der Register dann nur 2 Takte braucht gegenüber 7. Ist Dir wahrscheinlich auch klar, und Du wolltest es in der Demo nur etwas kompakter geschrieben haben.

Ich spare mir das ADD-Hochzählen der Register, indem ich z.B. schreibe ES:[DI+2244] (für das letzte DWORD).
Auch so eine Sache die alles komplizierter als nötig aussehen lässt, und bei MOVSD in der Form ja gar nicht möglich/nötig ist.

Da ich die unrolled-Variante bevorzuge, bei der (bis auf unrolled-Grenzen) nur gesprungen wird, wenn kopiert werden soll, verwende ich JCXZ. Das erlaubt leider nur einen Short-Jump, weshalb ich den recht langen Code der Kopiererei in eine Routine gepackt habe. Dadurch sind es 8 Takte mehr, was sich aber immer noch lohnt angesichts 16x MOVSD + 14x ADD. Also 40 (32 + 8) Takte vs. 126. Wegen Grafikspeicher vielleicht nicht verlässlich. Aber für die Restaurationsroutine könnte es dann hinkommen. Es ist ein rein- und rausspringen, dadurch kommen neben dem Cache-Flush noch 6 Takte dazu. Irgendwie nicht so schön, 14 Takte fürs Springen und CALLen/RETurnen, in der Routine selbst 34 Takte für's kopieren und abschliessende weiterzählen der Register um jeweils 8. Hmm, vielleicht noch nicht so optimal, muss ich noch drüber nachdenken. So wie ich's jetzt habe rasselt das eben sprunglos (bzw. alle 20 ein Sprung) mit nur 2 Takten pro Block durch, wenn nichts zu kopieren ist, von der Seite betrachtet ist es ziemlich optimal.

Ich bin mir nicht sicher, ob Deine Technik mit dem 32 Bit Register und Carry hier Vorteile bringt, denn ich muss ja auch implementieren dass alle 40 Blöcke die Kopier-Register um einen speziellen Wert erhöht werden, und auch BX (als Offset des BlockBoolean-Arrays, alle 20 Durchläufe, da ich hier wieder [BX+1] etc. verwende). Das geht am einfachsten und ohne Testerei des Zählers wenn man eine zweidimensionale Schleife hat, die geschwindigkeitsmäßig hier gar nicht so zu Buche schlägt, da ich die Schleife 20fach entrollt habe, was vielleicht an Wahnsinn grenzt, aber immer noch relativ kompakt aussieht und im Code auch nicht größer als ca. 200 Bytes sein sollte, da JCXZ noch funktioniert. (Klar, -128 ist die Grenze, aber ich springe ja z.B. von der untersten Zeile der Prüfliste in die unterste der Ausführliste, und da sind dann noch 19 drüber.) Wenn das 20-fach grober Unfug ist kann man es jederzeit auf 10 oder 8 kürzen. Bei so kurz gehaltenem Code war ich mir nur nicht sicher, ob man mit 20-fach das Prefetch nicht besser ausnutzt.

Im Wesentlichen geht es mir hier um Deine Meinung zu "händischem" kopieren über EAX gegenüber MOVSD, und ob so ein [DI+2244]-Konstrukt nicht Geschwindigkeit kostet.

Bitte kläre mich auf wenn ich da irgendwo falsch denke.

(Ich habe hier zwischenzeitlich viel editiert. Ich denke mal als nächstes mache ich ersteinmal einen neues Post.)

Re: Eigenes Videoformat

Verfasst: So 14. Jun 2020, 11:24
von DOSferatu
Zuerst muß ich wohl mal ein paar Dinge klarstellen/in's rechte Licht rücken:
1. Ich trete mit meinem Kram keinesfalls in "Konkurrenz" zu irgendwem oder irgendwas. Daß ich meine Erfahrungen bezüglich Sparen von Zyklen und ähnlichem hier preisgebe, bedeutet nicht, daß ich deshalb der Meinung wäre, daß mein Kram irgendwie performt.

2. TESTGAME.EXE ist kein Beispiel für Performance. Es ist ausschließlich ein Testtool, um die korrekte Funktion von GameSys2 (GS2.TPU) zu testen - also Opcodes, Adressierungsarten, Kollisionsmatrix. Die Textausgabe von dem Ding ist das Allerletzte: Zu Anfang war keine Textausgabe drin, dann hab ich eine gebaut - aus Faulheit die primitivst-mögliche: Wird wirklich in Pascal mit Schleifen gemacht und jeder Pixel einzeln von einer Pixelroutine gepixelt, der die Punktkoordinaten übergeben werden. Noch mehr SlowMo geht ja fast gar nicht. Das heißt: Wenn diese daß die Textausgabe an ist (sonst sähe man CTRLBF ja nicht), sieht man, wie sehr es mit und ohne Textausgabe ruckelt. (Auch bei Textausgabe funktioniert die normale Steuerung weiterhin.)

3. Meine Routinen sind NICHT totale Performance-Grenze - weil's mir nicht ausschließlich darum geht. In Xpyderz z.B. hatte ich diese Draufsicht brauchte ich also diese drehbaren Sprites. Und auch für künftige Spiele finde ich die Features meiner Sprites (drehbar/spiegelbar/skalierbar/zerrbar) recht nützlich und auch die Transparenz/Dithern und die Mehrebenen-Levels. Performt alles bekanntlich so lala... Auf schnellem 486er ganz OK, darunter nicht mehr so. Ich behalte mir vor, VIELLEICHT mal etwas an den Levels zu verbessern oder mal simple Sprites, ohne Skalieren/Drehen zu machen - die wären wohl auch schneller.
zatzen hat geschrieben:Zunächst einmal wieder ein kleines Disassemblat. Du wirst es wohl schon selber überprüft haben, bin mir aber nicht sicher, und wollte es selber einmal bestätigt haben. Es geht um PRED:
[...code...]
PRED ist also wirklich schneller, und bei -1 wird von Word- bzw. Integer-Breite ausgegangen, wohl für den Fall dass man noch mehr anstellt als x - 1.
Naja, hab das nicht wirklich getestet, hab's glaub ich bloß mal in der Pascal-Hilfe oder der SWAG gelesen.
zatzen hat geschrieben:Meine Befürchtung war, dass wenn das Spiel für die nur teilweise Rekonstruktion des Hintergrunds und ebenso nur teilweises Updaten in den Grafikspeicher ausgelegt ist, es sein könnte, dass bei einem kompletten plötzlichen Bildwechseln es zu einem Hakeln kommen könnte.
Naja, wie gesagt - ich habe das absichtlich langsam gemacht, damit man das "rein-mosaiken" des Bildes sieht, weil's schick aussieht. Ohne Bremse (ich hab einfach den nur den 18,2/sek Ticker benutzt) wär's wohl schneller.
zatzen hat geschrieben:Also, wenn man doch ein Spiel mit wenigstens "Blättern" machen will, wie Caldron II oder Prince of Persia. Oder Kotzman II. Daher dachte ich, wäre die Mosaik-Methode eine Möglichkeit, das ganze zeitlich über ein paar Frames zu verteilen bevor es im Spiel weitergeht. Zügiger natürlich, nicht 32 Blöcke pro Frame sondern z.B. 200. Mein Problem ist ja, dass es mir noch nicht eingegangen ist, den Sound quasi "im Hintergrund" für sich selbst laufen zu lassen. Ich sträube mich nicht gegen eine Alternative zum "Sound Timing", aber ich habe einfach keinen Schimmer, wie man die Berechnung der Sounddaten so koordiniert, dass diese gleichmäßig über die Leistung verteilt wird und nicht schubweise alles ausbremst.
Naja, ich mach's ja auch auf die relativ "lame" Art...
zatzen hat geschrieben:Daher bleibt mir nur das Prinzip, alle Berechnungen nacheinander zu takten, und dann wird in der Hauptschleife eben z.B. zuerst die Steuerung/Kollisionen berechnet, die Grafik, dann der Sound, und in der "freien Zeit" oder wohl besser per Interrupt die Tastatur abgefragt. Ergebnis ist dann eben, dass die Framerate konstant ist und die Leistung nicht voll ausgenutzt wird, oder, die Leistung reicht nicht und es hakt und der Soundpuffer loopt.
Ich mache es in meinen Games genauso: Eine Hauptschleife, in der nacheinander "immer im Kreis" die ganzen Dinge ausgeführt werden, wenn das "Flag" (bzw der Zähler) drüber ist. Aber in diesem Fall ist die Framerate NICHT KONSTANT - das ist ja gerade der Witz daran: Die Schleife rennt die ganze Zeit durch und das System schafft eben, was es schafft. Man muß dann aufpassen, daß selbst wenn die Schleife voll ausgelastet ist, der Soundpuffer immer nochrechtzeitig reaktiviert wird. Allerdings hat man ja einen ganzen Puffer "Zeit" dazu - denn, wenn die Karte neuen Sound braucht, schalte ich ja auf den schon "in der Zwischenzeit" den anderen Puffer berechnen lassen - also, sobald die Schleife an der Stelle "vorbeikommt"...
zatzen hat geschrieben:Im Grunde wie bei Deiner Demo, wo Du auf Retrace wartest, dort halbiert sich ja die Framerate auch direkt wenn es zu viele Sprites/Blöcke werden.
Ein Mißverständnis. Ich warte in TESTGAME nicht auf Retrace, soweit ich mich erinnere. Die Schleife hat nur immer mehr "Wartezeit" je mehr Sprites angezeigt werden müssen. Die Level-Routine und danach die, die die Sprites zeichnet, wird nicht für andere Dinge "unterbrochen" (außer durch Interrupts). Alternative wäre "Multitasking" - hatte sowas schonmal angedacht, allerdings hat es den Nachteil, daß viel Stackspeicher benötigt würde - quasi für jeden Prozeß einen eigenen Stack!
zatzen hat geschrieben:Unnötig zu erwähnen, dass Du in richtigen Spielen die Sache anders angehst. Aber vielleicht komme ich ja mit meiner Sache durch. Ich habe in Hakeln. Dein TestGame hat bei dieser Leistung gerade mal ca. 5 fps. Unten dazu mehr und vorab: Keine Wertung, nur Feedback. Ich habe solche umfangreichen Dinge noch nie programmiert und kann mir kein Urteil erlauben.
Ja, wie bereits ganz oben erwähnt: TESTGAME ist KEIN Beispiel für Performance, sondern nur ein Testprogramm für GameSys2. Daß es wie ein "Spiel" aussieht, liegt daran, daß es viel zu kompliziert und aufwendig gewesen wäre, irgend ein komisches Testprogramm zu bauen, was die Kollisionsbefehle "trockentestet" - aus so Werterückgaben wär's sehr umständlich herauszufinden, ob die Kollsionen korrekt ablaufen - da ist es einfacher mit angezeigten Hitboxen und direkter Anzeige, was womit kollidiert ist - und das "Laufen" und reagieren auf's Level (Boden, Wände, tödliche Spitzen) dient auch nur dem Test der Figur-Level-Kollisionen.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Alles was in einem boolean nicht 0 ist, wird als WAHR gewertet.
Ich war nur zwischendrin schonmal verunsichert quasi über eine offizielle Definition. Ich meine irgendwo (vielleicht Basic) TRUE als -1 (also $FFFF) gesehen zu haben, woanders aber meist mit 1, also nur ein Bit gesetzt...
Naja, das stimmt z.B. für die BASICs, die ich so kenne. Da ist FALSE=0 und TRUE=-1, (also $FFFF). In Turbo-Pascal ist es 0 und 1 - wenn Pascal es selbst setzt. Beim Auswerten von Booleans wird aber alles <>0 als TRUE genommen. Man muß nur aufpassen, falls man z.B. ord() benutzt - da wird dann der "richtige" Wert zurückgegeben (da hilft dann ord(Wert<>false), das setzt es wieder auf 0 oder 1) oder wenn man so ein Entscheidungsarray wie

Code: Alles auswählen

const D:array[false..true]of byte=($4E,$2F)
benutzt (nur so Idee, um z.B. Farben zu setzen abhängig von einem Ergebnis)... - da muß dann natürlich wirklich intern nur 0/1 sein, weil das ein Index ist, der wird dann nicht bereinigt (kann aber auch mit D[Wert<>false] gelöst werden.)
zatzen hat geschrieben:Praktisch, mit den Booleans in Verbindung mit absolute... Das mit der Länge bei Strings hattest Du mir ja schon vor Längerem erzählt. Bei Pointern habe ich zuletzt, beim portieren des Beni-Players, immer Offset und Segment als Word nacheinander definiert und einen Pointer mit absolute auf die Offset-Variable deklariert, weil ich LDS bzw. LES einsparen wollte, braucht ja mehr Takte als wenn man die Register direkt aus Variablen speist. Siehst Du soetwas auch als sinnvoll an? Oder vielleicht sogar "gefährlich" weil evlt. wegen der Speicherauslegung zwei Words nicht genau nebeneinanderliegen?
Die liegen IMMER hintereinander - das ist kein Problem. Ich benutze sowas auch. Unbedingt aufpassen muß man hier bei "lokalen" Deklarationen im Header einer Funktion/Procedure: Lokale Variablen werden auf den Stack gelegt und Pascal füllt den einfach so, wie ein Stack gefüllt wird: Rückwärts! D.h. alle innerhalb von Procedures/Functions deklarierten VAR-Variablen usw liegen rückwärts, d.h. mit absteigendem Offset im Speicher (also Stack). Wenn man also Spielereien wie das Obengenannte machen will, muß man das beachten! Da kann man leicht drauf reinfallen!
zatzen hat geschrieben:
DOSferatu hat geschrieben:Wie gesagt: Pascal nur der Übersichtlichkeit wegen - damit man sieht, wann und womit ein Block aufgerüfen wird. Dir ist ja sicher aufgefallen, daß der Block selbst KEINE Multiplikation benutzt, um den Offset im Speicher zu berechnen, sondern mit ein paar einfachen Additionen/Shifts das Gleiche hinkriegt.
Ja, hab ich nachvollzogen. Da ist mir noch ein kleiner Überflüssigkeitsfehler aufgefallen, das erste "mov si, ax" kann weg.
Stimmt.
zatzen hat geschrieben:Wenn ich das ganze später so gestalte dass alles in einer Routine gemacht wird, kann man wohl alles durch reine Addition adressieren. Und ich werde FS brauchen, weil man ja die Blockinfos liest, mit den zu lesenden Grafikdaten hantiert, und diese auch wohin kopieren muss.
Klar. Multiplikation vermeide ich in Assembler auch, wo ich kann. Und, daß Du FS brauchst, sollte kein Problem sein: Wenn Du sowieso auch 32-Bit-Zugriffe machst (was erst ab 386er geht), kannst Du auch getrost FS/GS benutzen - die gehen ja auch erst ab 386er. Und FS/GS sind übrigens auch nicht langsamer als DS/ES.
zatzen hat geschrieben:Das Prinzip ist mir so schon klar, ich hab es mir ja ausgedacht... Aber:
DOSferatu hat geschrieben:Es werden aber ständig alle Sprites-enthaltenen Blöcke kopiert - auch bei denen, die ihre Position nicht ändern. Das könnte man noch anpassen, aber:
Ja, das ist eine interessante Idee. Wäre auch wünschenswert wenn man z.B. eine Ecke eines Levels mit Boni zupflastert die sich nicht bewegen. Und die Umsetzung dessen wäre auch erstrebenswert, wenn es nicht wieder extra Performance kosten würde. Vielleicht könnte man eine Art Hardware-Sprite Emulation aufbauen, und von Grund auf nur alles relative bzw. was sich verändert überhaupt berücksichtigen, also auch beim Setzen von Sprites. Nur so eine Idee, wäre sicherlich alles kompliziert. Und mit Kanonen auf Spatzen geschossen wenn ich letztlich nur so ein 20x20 Pixel Sprite Spiel mit 20 Sprites habe.
Naja, ich denke, daß man wahrscheinlich mehr Probleme als Vorteile daraus ziehen wird - ist aber nur meine persönliche Meinung. So ein "statisches" Sprite darf sich dann weder bewegen/animieren, noch durch andere Figuren gekreuzt werden - sonst gibt es bei der Wiederherstellung immer Probleme. Man muß es ja leider 2-stufig machen: Erst die komplette (Wieder-)herstellung ALLER Bereiche, die von Sprites belegt sind oder im vorigen Frame waren und dann alle Sprites zeichnen. Ja, das klingt wie doppelte Arbeit - wieso kann man nicht gleich die Blockwiederherstellung und das Sprite-Zeichnen für jedes Sprite direkt nacheinander machen, wenn man schonmal die Werte hat? Weil man damit überdeckte (bereits in diesem Frame gepixelte) Sprites wieder teilweise oder vollständig löschen würde.
zatzen hat geschrieben:
DOSferatu hat geschrieben:und NATÜRLICH ist eine 16000-DWORD Kopierroutine (REP;MOVSD) schneller als 1000 Block-Aufrufe.
Auf jeden Fall bzgl. der Demo (völlig klar habe ich verstanden dass die schwächer performt weil sie illustrativ in Pascal gehalten ist).
Nicht nur deshalb - sondern weil natürlich eine durchgehende Routine schneller ist als eine, die jedesmal einzeln zur Blockposition muß, also diese vorberechnen und mit Einzelbefehlen einen Kasten füllen.
zatzen hat geschrieben:Und klar, dass 1000x BlockGefummel, selbst in bestem Assembler, langsamer ist als 64000 Byte per REP MOVSD.
Das wollte ich damit sagen.
zatzen hat geschrieben:Der Datendurchsatz bei 20x20 Sprites wäre, über den Daumen gepeilt, was Restaurieren und in den Grafikspeicher kopieren angeht, vielleicht 20000 Byte. Komplett kopieren wären 128000 Byte, also das über 6-fache. Wenn ich da wenigstens nur um Faktor zwei bis drei gebremst bin durch das Block-Info- Gefrickel und den zerteilten Kopiervorgang statt REP MOVSD, dann würde sich das lohnen.
Ja, meiner Einschätzung nach lohnt es sich wirklich maximal bei einer einstelligen Anzahl (1-9) "kleiner" (max. 32x32) Sprites. Bei allem, was darüber ist, wird man meiner unmaßgeblichen Meinung nach kaum noch Performance rausholen, sondern eher mit komplizierten Routinen herumhadern, die am Ende gar nicht wirklich helfen.
zatzen hat geschrieben: Wenn nicht, könnte ich das verwerfen und über Scrolling nachdenken...
Dazu greife ich hier mal ein Zitat vor:
DOSferatu hat geschrieben:Naja, Mode-X heilt auch nicht alles. Mode-X macht nur Sinn, wenn man immer den ganzen Screen neuzeichnen will und dann quasi sich das "zusätzliche Kopieren ins VGA" sparen will. Prinzipiell kann man wahrscheinlich sogar schneller sein, wenn man das Frame in einem 64000 Puffer erstellt, die Sprites draufklatscht und dann alles in VGA kopiert (16000 DWord-Zugriffe), als wenn man wie ich das Level und die Sprites direkt in VGA (Mode-X) Speicher schreibt, denn das sind ÜBER 64000 BYTE-Zugriffe auf VGA-RAM.

Es wären ja schon mehr als 64000 Zugriffe wegen 320x240.
Dazu nochmal eine Erklärung meinerseits:
Mit "Mode-X" bezeichne ich zusammenfassend alle Modi, die durch das Unchaining (und die damit verbundene komische *4-Adressierung) die vollen 256kByte VGA-RAM benutzen können. Daß damit auch 320x240 geht, ist nur der Nebeneffekt, weil man dann keinen "Wraparound" mehr hat, wenn man den einstellt. Anders ausgedrückt: Den "Mode-X" gibt es auch für 320x200 - da hat man dann 4 "Bildschirmseiten" - bei 320x240 hat man 3, usw.: Anzahl_Seiten=262144 div (Breite*Höhe).
Und ja, da gibt's auch Leute, die dann die Sachen so benennen wie:
Mode-X: VGA 320x240, unchained
Mode-Y: VGA 320x200, unchained
Mode-Q: VGA 256x256, unchained - UND chained (ja, der geht auch MCGA!)
Aber ich benutz da nicht das Alphabet, um die Auflösungen durchzubuchstabieren, sondern nenne es einfach Mode-X, kombiniert mit der Auflösung.
(Anm.: Der Mode-X 320x200 wird übrigens in DOOM1+2 benutzt.)
zatzen hat geschrieben:Nunja, auch durch unseren Exkurs vor ein paar Wochen in einem anderen Thread zum Thema Mode-X, erschien mir die Programmierung für MCGA mehr und mehr als regelrecht rückständig und veraltet. Und man will natürlich keine neuen Spiele schreiben wenn man dabei das Gefühl hat "es könnte alles zig mal besser performen und ich hab sooo viel mehr Speicher wenn ich Mode-X benutze" oder auch "wenn ich für MCGA programmiere ist das alles für die Katz und total amateurhaft".
Naja, wie gesagt: ALLES, was man irgendwo einsetzt, hat seinen Sinn und "Unsinn" - Mode-X (oder andere Spielerchen) zu benutzen, nur weil "es cool ist" - das kann man ja für so Experimente durchaus machen. Wenn man aber echte Ergebnisse haben will, ist es keine schlechte Idee, alle Optionen abzuwägen, anstatt einfach "irgendwas neues" zu nehmen, nur weil irgendwer gesagt hat, daß das was bringt. Ob es etwas bringt, zeigt immer nur die Praxis.
zatzen hat geschrieben:Fakt ist einfach, mit MCGA kenne ich mich aus (ist ja auch kein Kunststück) und ich kann mich dann erstmal wirklich auf ein Spiel konzentrieren bevor ich mich monatelang mit Mode-X beschäftige und meine gefestigten Erfahrungen der letzten knapp 30 Jahre quasi über den Haufen werfen muss.
Naja, wie schon gesagt: Es nur zu benutzen um des Modus willen ist wohl eher eine blöde Idee. Wenn man seine Routinen nicht dafür abstimmt, spart man zwar immer noch RAM (wenn man direkt in die Grafikkarte schreibt, Doublebuffer), aber durch die andersartige Adressierung erntet man nur Performanceverlust, wenn man den Kram nicht anpaßt. Meine "Mode-X"-Routinen arbeiten z.B. senkrecht, d.h. die "innere Schleife" geht zeilenweise und die äußere spaltenweise. Das ist umgekehrt wie man normalerweise vorgehen würde - aber man spart Zeit, weil man die "Planes" weniger oft umschalten muß
zatzen hat geschrieben:Was bleibt, ist das Problem mit dem Sound-Timing, wobei ich durchaus sagen muss dass wegen der viel von uns kontrovers diskutierten Synchronität von Bild und Ton - ich habe gerade nochmal ein Spiel gesehen wo diese nicht gegeben war und fand das ziemlich nervig, störend für das Spielerlebnis.
Wahrscheinlich bin ich da unempfindlicher - oder habe so ein Spiel noch nicht gehabt
zatzen hat geschrieben:Ich kenne auch genug Spiele, bei denen die Framerate nie einbricht. Da wäre dann so gesehen die Implementation einer variablen Framerate nur überflüssiger Code und damit eine gewisse Verlangsamung.
Ich hatte das oben schonmal angerissen: Variable Framerate muß nicht extra implementiert werden - die ergibt sich von allein, wenn man eine Dauerschleife nutzt, die alle Prozesse immer wieder nacheinander (wenn "angefordert") ausführt. Eine fixe Framerate bringt Verlangsamung: Denn sie ist immer gleich schnell (oder langsam) - egal, ob noch Zeit wäre oder nicht. Und sie hat den kleinen Nachteil, daß, wenn das System zu langsam ist, um alles innerhalb dieser fixen Rate zu berechnen, dann GAR NICHT mehr funktioniert, wogegen eine variable Rate das Spiel vielleicht mal etwas mehr ruckeln läßt, wenn viele Sprites gleichzeitig da sind - aber das Ganze nicht abgebrochen werden muß.

Anm.: Ich habe mal so ein Spiel gemacht - die Steuerung aller Figuren geschah im Timer-Interrupt. Wenn das System zu langsam ist, funktioniert's quasi gar nicht mehr richtig. Mehr dazu weiter unten.
zatzen hat geschrieben:Anders ist das oft z.B. bei 3D Shootern, die fluktuiert die Framerate meistens sehr stark.
Naja, ich würde sagen, bei 3D-Shootern fällt es nur mehr auf - weil die Anzahl zu zeichnender Wände/Figuren (vor allem, wenn 3D softwareseitig - also durch CPU statt Grafikkarte berechnet) sich erheblich unterscheiden kann, je nachdem, wo im Level man sich gerade befindet und wohin man schaut. - Habe ja auch schon selbst 3D-Zeug (eher so 2,5D-Zeug wie Doom) gebaut.
zatzen hat geschrieben:Eine mit 400 Hz getaktete Steuerung ist ein anderes Thema. Ich werde sehen wie ich die Steuerung und Tastaturabfrage integriere.
Da beziehst Du Dich hier wohl auf meine Bemerkung zu Xpyderz. - Das hatte ich dann wohl nicht genau genug erklärt:
Xpyderz hat einen Spielstep von 1/50 Sekunde, d.h. jede Figur macht 50 Schritte pro Sekunde. Die 400Hz ver-8-fachen das Ganze, und in jedem 1/400-Tick wird nur jede 8. Figur gesteuert - unnötig kompliziert. Die Idee dahinter war, daß für ALLE Spieler-Figuren dabei immer auf dem 1. Tick die Steuerungseingaben abgefragt werden und sie erst im 8. Tick dann damit gesteuert werden. Der Grund dafür war, daß Xpyderz mal Multiplayer werden sollte (hatte das schon mehrmals erwähnt) und daß zwischen 1. und 8. Tick genügend Zeit bleiben sollte, um die Daten entsprechend an die anderen Spieler zu übertragen (über serielle Schnittstelle, später auch Ethernet/Internet). Das ist der einzige Grund für diese 400Hz.

Natürlich könnte man das auch entsprechend abwandeln: Die Levelroutine kann nur einen Teil des Levels zeichnen, die Spriteroutine zeichnet nur ein paar Sprites. Erst wenn das ganze Level/Sprites fertig wären, wird auf das Frame umgeschaltet. Und das Gleiche für die Figuren: Die Steuerroutine steuert immer nur 1/8 der Figuren (natürlich jedesmal ein anderes Achtel) - auf diese Art müssen die Routinen zwar öfter aufgerufen werden - aber man hätte zwischen den Routinen mehr Zeit, um "dringende Dinge" (wie Soundpuffer-Refresh) zu erledigen. Allerdings macht so etwas die Routinen etwas komplizierter und - durch die mehrmalig nötigen Aufrufe - auch etwas langsamer.

Das gerade Beschriebene wäre schon etwas wie "Soft-Multitasking". Ich beschreibe grad der Vollständigkeit halber noch das "Harte", was ich anfangs schonmal kurz angedeutet hatte:
Man hat ebenfalls eine "kreisende" Schleife - diese hat eine bestimmte Anzahl Slots und sie kreist immer vom ersten bis zum letzten Slot und dann wieder zum ersten. Kann auch Slots hinten anfügen, sobald ein Prozeß dazukommt. Klingt erstmal wie die "Spielschleife" - aber: Der Unterschied ist, daß der Ticker nun entsprechend mißbraucht wird: Bei jedem Tick macht er folgendes:
1. Alle Register (inkl. Segmentregister) auf den Stack dieses Prozesses sichern.
2. Den Stack eins weiterschalten (wenn Ende überschritten, wieder auf ersten).
3. Alle Register (inkl. Segmentregister) vom neuen Stack den nächsten Prozeß holen.
Der allererste Prozeß ist der "interne" für die Steuerung und das Ein- /Ausklinken anderer Prozesse.

Mit dieser Variante hätte man ein Multitasking - jeder Prozeß würde einfach zyklisch durch einen Ticker-Int unterbrochen werden - die Prozesse wären alle "nebenläufig" und jeder (vor allem Sound) würde rechtzeitig reagieren, da man den Ticker so einstellen würde, daß er häufig genug anspricht. Zusätzliches Priorisieren von Prozessen wäre natürlich zusätzlich möglich.

Nachteil: Performanceverlust, weil der "Hauptprozeß" immer mit dabei wäre und weil dieser "Ausführungs-Stern" ja am Laufen gehalten werden muß (zyklisch Status auf Stack sichern/laden). Und, nicht zu vergessen: Jeder Prozeß bräuchte dafür seinen eigenen Stack - bei der normalen Spielschleife beenden sich ja die Prozesse und der nächste wird aufgerufen. In dieser simplen "Multitasking"- Variante dagegen enden die Prozesse nie, sie würden laufen und laufen, bis das Hauptprogramm ihren Slot freigeben würde. Auf einem DOS-Rechner, der nur den Heap für den Stack nutzen kann, sollten die Einzelprozesse dann nicht zuviel Stack benötigen, sonst wäre nicht mehr genug für Daten (Grafik, Sound, Level) da...

Ich selbst habe dieses Konzept bisher nur mal theoretisch durchdacht - habe noch nie eine solche "Multitasking-Umgebung" wirklich gebaut/genutzt.
zatzen hat geschrieben:Zu Scrolling noch eine Bemerkung: "Blättern" hat auch seinen Reiz. Man sieht nicht direkt was einen im anderen Bereich erwartet, und innerhalb eines Screens ist alles viel aufgeräumter.
Ach, ob der Screen "aufgeräumt" ist, wäre mir egal. Aber ja: Es gibt viele so Spiele mit "Blättern" - viele ältere Labyrinth-Spiele sind z.B. so. Ich hätte damit auch kein Problem - solange ein Spiel Spaß macht, sind Dinge ob es scrollt oder nicht für mich nebensächlich.

Anm.: Falls Dir "Blättern" gefallen sollte: Mein C64-Spiel "Rockus" scrollt z.B. auch nicht - da wird auch geblättert: 16 Bildschirmseiten pro Level, 27 Levels (9 Hauptlevel à 3 Unterlevel) - und es ist ein Jump'n'run - und ja, sogar mit Musik.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ich meine damit das "Zeichne großes Bild auf 20 m²-Wand" Ding: Ab und zu, wenn ich mal eine gute Idee zu meinen habe und mich tage-/wochenlang verliere, gehe ich auch mal "einen Schritt zurück" (von der "Wand") und gucke mir das "Gesamtergebnis" an - ob es noch das ist, was ich eigentlich wollte...
Ja, ich habe ja auch noch keinen "Pinselstrich" getan. Bisher alles nur Überlegungen. Ganz am Anfang meiner "Karriere" hab ich schneller drauf los programmiert als nachgedacht.
Komischerweise kommt bei mir da immer das meiste raus. Die langfristig geplanten Projekte kriegen zwar "immer bessere" Pläne und Überlegungen - dafür aber wenig "Vorzeigbares", wenig "Nutzbares", allgemein wenig "Code". Seit Jahren will ich ja nun mal wieder ein Spiel machen, halte mich aber seit Ewigkeiten mit quasi "administrativem Kram" auf...
zatzen hat geschrieben:Das ist jetzt ziemlich umgekehrt, und solche Kompressionsüberlegungen sind natürlich nicht der Weisheit Schluss. Kommt vielleicht einfach auch aus dem Gedanken des ollen ZVID(1) raus: Das lief mit all seinen ultrakomplizierten, eher schlecht programmierten Entpackvorgängen immer noch relativ performant. Und ich hab's in meiner ZSMPlay Moonwalker Demo verwendet als alleiniges Grafikmedium (auch der Boden, war ja ein ZSMPLAY-Modus, und da wollte ich Speicher sparen wo es nur geht, damit was frei bleibt fürs ZSM Modul). Kannst ja nochmal reinspinksen: https://youtu.be/uhuQgSfkg5M
Ja, ich erinnere mich. Schon recht ulkig.
zatzen hat geschrieben:Natürlich Framerate im Bereich von nur ca. 25 fps und nicht der ganze Bildschirm aktiv, und ZSM war zu dem Zeitpunkt noch nicht delta-komprimiert. Aber: Nur 20000 Cycles. Dürfte jeder noch so schlappe 486er oder sogar ein guter 386er packen.
Ja, ich denke schon.
zatzen hat geschrieben:Mittlerweise könnte ich mir vorstellen, ZSM eine 8K-Tabelle zur Beschleunigung der Lautstärkeeinrechnung zu spendieren, ich glaube das schlägt sogar mehr zu Buche als die Deltakompression im Schnitt. Ich hab so Chiptune-artiges genug bewundert und konzentriere mich lieber auf Spiel-relevante Sounddatenmengen im 100-200 K Bereich.
Naja, ich liebe ja dieses Chiptune-Zeug. Für mich ist das ja nicht nur "aus der Not heraus" (Speichermangel/Performance usw.), sondern, weil ich es wirklich so haben will.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Naja, wie gesagt: Das "Hauptbild" könnte aus 1000 "Zeigern" bestehen, die auf die wirklichen Blöcke zeigen. Bei gleichen Blöcken ist der Zeiger gleich. Und da hier noch etwas "Luft" ist, hätte man zusätzlich noch Optionen, um gleich im "Zeiger" auch noch "Vorgaben" für verschiedene Arten von Packen zu haben
Ja, ich hatte auch spontan eine Offset-Tabelle angedacht, nichts anderes ist das ja was Du sagtest. Bei 10 Bit müsste man nur jedesmal shiften, oder auch wenn man es so macht mit FFxx jedesmal prüfen und und einen bedingten Sprung einbauen...
Naja, das mit dem Shiften usw hatte ich nur erwähnt, weil man vorher nie weiß, wie gut etwas packt - man aber, wenn man sich erstmal auf die 1000-Words-Liste eingelassen hat, diese auch nutzen müßte, auch wenn sich Blocks NICHT wiederholen (wenn das Bild "unpackbar" ist). In diesem Fall hätte man 2000 Bytes für Blockzeiger und 64000 Bytes Bilddaten - also zusammen 66000 Bytes - was leider gerade ein bißchen (464 Bytes) mehr als ein volles 64k-Segment wäre, so daß die Routinen dafür wieder etwas komplizierter würden als nötig, wegen der möglichen Segmentüberschreitung. (Eine andere Lösung ist dann die 2000 Zeiger und die bis zu 64000 Daten an 2 verschiedene Segmete zu legen, aber so viele Segmentregister hat der x86 nunmal nicht und die kann man woanders besser gebrauchen. Diesen Gedanken ist letztens die Überlegung entsprungen, die Zeiger bitweise zu "shiften" - um nur 1 Segment für Zeiger+Daten zu brauchen.
zatzen hat geschrieben:Aber geht ja nicht anders wenn man Alternativen zum einfach-64-Byte kopieren will. Bei Deinem Vorschlag mit dem oberen Byte > 249 spart man jedenfalls 1000 Byte, die ich sonst regelmäßig als Blocktyp-Info reingebracht hätte. Zum Glück ist CMP nicht so langsam wie es verschrien ist.
CMP ist ein ganz schlichter Befehl - macht nichts anderes als eine Subtraktion ohne Speichern des Ergebnisses, sondern nur der sich ergebenden Flags. Braucht also auch nicht länger als eine normale Subtraktion und die ist, was Register angeht, auf 486er glaub auch bloß noch 1 Zyklus lang.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Außerdem ist es gar nicht mal so einfach, gerade mal 64 Bytes so zu packen, daß die "Meta-Daten" (Header, "Tabellen" usw.) daß es sich lohnt.
Bei ZVID2 (sorry falls ich Dich immer damit nerve)
Naja, es ist eben Deine Referenz. So wie meine GameSys2, Arcade01, usw. sind. Um mich wirklich zu "nerven", bräucht's schon etwas mehr...
zatzen hat geschrieben: sind die Verhältnisse noch "enger", nur 16 Bytes, und hier ergeben sich nach bisherigen Tests mit GIF vergleichbare Ergebnisse. Schon allein weil man höchstens 4 Bit braucht für einen 4x4 Block und zusammen mit dem nur einem Block-Infobyte ein Komprimierungsverhältnis von 9:16 (56%) garantiert ist, mit Tendenz zu 7:16 (44%) und 5:16 (31%) oder eben 1 Bit, 3:16 (19%), geschweige von einfarbigen Blöcken, 1:16 (6%), die so gut wie immer auch vorkommen in Sprites, allein wegen transparenten Flächen. Also, je kleiner die Blöcke werden, desto wahrscheinlicher werden auch sehr niedrige Bitaufkommen, man muss dann nur die Blockheader auch klein halten, hier eben 1 Byte.
Naja, bei mir sind so "Flächen" eher die Ausnahme, bin da nicht wirklich begeistert von so "flächiger" Grafik. Und meine Sprites sind normalerweise nicht so dilettantisch "gepackt" wie in diesem PCX zu TESTGAME - normalerweise geht der "Rahmen" um den Sprite nur direkt bis an die jeweils äußeren Punkte. Somit sollten kaum "Flächen" auftreten, auch kaum "Transparenz-Flächen". Wenn ich wirklich ein großes Sprite hätte, was um sich herum viel Transparenz hätte (was ja auch in der Darstellung dann mehr Power bräuchte, um die transparenten Punkte zu "überspringen"), würde ich es wohl eher aus mehreren kleineren Sprites zusammensetzen - einmal, damit es Platz spart und natürlich auch (so große Sprites wären ja eher so "Endgegner/Endmonster"), um einzelne bewegliche Teile dran zu haben, was meist besser aussieht als ein unbeweglicher großer "Spriteklotz". Wenn man da nur ein einzelnes Sprite macht und alle Bewegungsphasen sind dann einzeln, obwohl sich nur kleine Teile daran bewegen, bräuchte es riesigen Speicher. Wenn man aber (wie ich) sogar drehbare und spiegelbare Sprites hat, kann das Ding z.B. Arme/Flügel/Flossen haben, die nur ein einzelnes Sprite sein müssen und sich um einen Drehpunkt bewegen...
zatzen hat geschrieben:ZVID2 hat natürlich noch einen kleinen Rattenschwanz Metapalette, der aber nimmt, weil maximal 16 Farben in den Blöcken vorkommen, im Schnitt eher 4-8 Farben. Bei den Sprites Deines TESTGAMEs wäre die "Ausbeute" sehr hoch. Klar, dass das meiste nur demomäßig mit wenig Farben gepixelt ist und es in einem echten Spiel anders wäre, aber eben nur mal als Beispiel was wir beide nachvollziehen können.
Ja, wie schon erwähnt: Eher Test-Tool und Machbarkeitsstudie.
zatzen hat geschrieben:Wobei die Kugeln und Pilze durchaus "spielreif" aussehen, und ich bei den Figuren besonders das Hinhocken gut animiert finde.
Tja, manchmal hab' ich so meine Momente. Aber so richtig gute Grafiken bekomme ich kaum hin - das ist alles eher so hingemurkst. Talent sieht anders aus.
zatzen hat geschrieben:Und in der Demo ist es auch beeindruckend wie z.B. die Kugeln um die Figuren herumkreisen, soetwas könnte ich natürlich mit meinen Routinen nicht bzw. nur in "2D".
Naja, das sind alles Dinge, die GameSys2 bzw. Arcade01 von sich aus schon "anbieten", da nutze ich mal die "Prioritäten"-Option aus und die Vergrößerung -und außerdem die Winkelfunktionen.
zatzen hat geschrieben:Für die Sache mit den Blockroutinen und für die Kompression der Hintergrunddaten hätte(!) ich angedacht, keine extra-Tabelle anzulegen, sondern die benötigten "Meta"-Farben direkt in den jeweiligen Blockdaten an die Bitdaten und eben nur bis max. 4 Bit, darüber unkomprimiert. Angabe der Länge der "Palettchen" wäre überflüssig. Müsste man dann durch zwei-Register-Adressierung bewältigen können also z.B. [SI+BX], aber genauer habe ich mir darüber noch keine Gedanken gemacht, ich bin ja immer noch in der Brainstorming-Phase.
Zum Glück macht die Art der Adressierung schon ab 286er keinen Unterschied mehr in der Performance. Beim 8086er waren es noch etliche zusätzliche Zyklen, je nachdem, ob mit 1 oder 2 Registern oder ob noch zusätzlicher Konstante ("Displacement") oder nicht. Das ist auch z.B. beim Shiften/Rotieren ganz gut: Ab 286er erhöht die Anzahl Schiebungen/Rotationen nicht mehr die Zyklen.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Und, was dazukommt ist: Sobald etwas byte-weise gepackt ist, verliert man den Geschwindigkeitsvorteil der 32-Bit-Zugriffe, d.h. die Anzahl auszuführender Befehle wird mindestens vervierfacht.
Das steht ausser Frage. Wenn man auf die ganze Blocktechnik pfeift könnte die Kompression des Hintergrunds lediglich eine alleinige Daseinsberechtigung haben, nämlich wenn man davon viele im Speicher halten will, diese redundant sind und großflächig aus wenigen Farben bestehen, und man schnellere Dekompression braucht als es ZVID(1) liefern kann. Soll nicht heissen dass ich sowas jetzt mache.
Naja, deshalb bin ich ja so ein Freund von Kacheln. Zum Einen ist das bei diesen "alten" Spielgenres sowieso eher schon normal, zum Anderen braucht man keine zusätzlichen Metadaten, um auf's Level zu reagieren, weil die Kacheln selbst (durch ihre Kachelnummern) ja diese Daten liefern und es natürlich eine Möglichkeit ist, viele große "Levelräume" (bzw. ein großes Level) zu haben, das trotzdem noch in den Speicher paßt. Natürlich sehen sich die Räume dann etwas ähnlich, wegen der "gleichen" Kacheln - wobei man mit bis zu 256 Kacheln in Kombination ja schon einiges anfangen kann, und man kann ja verschiedene "Welten" mit unterschiedlichen "Themen" haben und diese haben dann jeweils einen anderen Satz Kacheln.

Und ich denke, genauso wird es auch bei dem von Dir schon öfters erwähnten "Lost Vikings" gemacht - sieht man ja schon: Das sind keine Vollgrafikbild-Hintergründe, das sind Pattern-/Kacheln-Levels. Wenn es gut gemacht ist, fällt das beim Spielen auch nicht so auf. Ich denke, sogar das farbenfrohe "Rayman" ist intern gekachelt.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Beim PC kann man da leider auch nicht wirklich in eine bestimmte Richtung optimieren, weil man nicht weiß, was hier die Bremse ist, weil jeder PC anders ist - Beispiel: Bei PC mit schnellem VGA-Durchsatz aber langsamer CPU müßte man in die entgegengesetzte Richtung optimieren als bei PC mit langsamer VGA und schneller CPU. Wenn beides langsam ist, ist da wieder eine mittlere Speed-Optimierung am besten und wenn beides schnell ist, aber wenig Speicher da, optimiert man auf Speichersparen und nicht auf Geschwindigkeit...
Und das ist mir dann irgendwie zu hoch, und da erlaube ich mir lieber, zumal wir nicht mehr im Jahr 1990 leben, einen flotten 486er vorauszusetzen der dann etwas zaubern soll was auch ein 386er gekonnt hätte wenn ich nicht ich sondern ein besserer Coder dran gesessen hätte.
Naja, ich wollte damit nur ausdrücken, daß es eben verschiedene PCs gibt und man selbst bei einem 486er nicht bestimmte Dinge einfach voraussetzen kann, was z.B. Geschwindigkeit der Komponenten angeht.

Ich weiß nicht, ob ich das Beispiel schonmal gebracht habe: Ich habe ein Benchmark-Tool hier, das den Datendurchsatz von Grafikkarten mißt. Ich hatte mal eine orginale VGA (ISA) - gemessener Durchsatz: 0,5 bis 0,6 MB/sek. Mein treuer 486er hat aber seit Jahrzehnten nun eine VGA/VESA-Grafikkarte (PCI) drin, deren Datendurchsatz ca. 16,6 MB/sek ist. Das nur mal so als Beispiel, welche Unterschiede man allein bei Grafikkarten voraussetzen kann. Genauso ist es mit der CPU: Wenn der Chipsatz des Motherboards nicht so besonders ist, kann die CPU zwar intern schnell sein, aber z.B. externe Zugriffe (d.h. RAM-Zugriffe) dann entsprechend langsam. Ich hatte die gleiche CPU früher mal auf einem anderen (eher langsamen) Board drauf - das jetzige Superboard ist ein Unterschied wie Tag und Nacht.

Das bedeutet: Wenn der Boardchipsatz gut ist und die CPU langsam, müßte man eher mit Speicherzugriffen arbeiten. Wenn die CPU schnell, der Chipsatz aber Mist ist, dann eher auf CPU. Das soll nicht heißen, daß man nicht optimieren sollte - im Gegenteil! Es soll nur sagen, daß die CPU-Zyklen nur ein Teil der ganzen Rechnung sind. RAM-Zugriffe hängen z.B. von der Zugriffsgeschwindigkeit der RAM-Bausteine ab - bei (langsamem) Grafikkarten-RAM noch deutlicher. Und immer, wenn die CPU einen RAM-Zugriff macht, muß sie auf den RAM warten.

Und das meine ich damit: Wenn man z.B. komplizierte Pack-/Verschlüsselungs- usw.- Verfahren macht, die zusätzliche Speicherzugriffe/Tabellenzugriffe erfordern, sind es nicht nur die reinen CPU-Zyklen, die da "bremsen" können.

Leider kann man fast immer nur entweder auf Speichersparen oder auf Geschwindigkeit optimieren.
zatzen hat geschrieben:Hatte ich ja quasi ähnlich schon im letzten Schrieb formuliert. 640K ist nebenbei schon irgendwie ne Einschränkung für mich und meine Ideen, deshalb eben die Formate mit Z vorne dran...
Naja, wie Bill Gates schon sagte: "640k ought be enough for everyone."
Ich finde, wenn man so sieht, was sehr viele Leute so mit diesen 640kB hingekriegt haben (und gab es Zeiten, in denen kaum 500kB wirklich frei waren bei vielen Leuten), sollte man sich fragen: Sind 600kB zu wenig - oder meine Anforderungen zu hoch für ein DOS-Spiel?

Ich hatte immer gedacht, das Z stünde für zatzen. (Was natürlich die nächste Frage aufwirft, nämlich, wofür zatzen eigentlich steht...)
zatzen hat geschrieben:Ich arbeite ein bisschen nach dem Prinzip, sich was einigermaßen vernünftiges technisches ausdenken mit der Absicht es in einem Spiel zu verwenden. Weniger, Spiel anfangen und dann "improvisieren". Das habe ich notgedrungen bei Kotzman II so gemacht und es war eher ärgerlich.
Ja, so geht's mir ja leider mit Xpyderz. Das "einfach anfangen" hat zwar ein spielbares Etwas hervorgebracht - aber leider auch Programmcode, den ich ungern jemals wieder anfassen will... "Von außen" mag es noch wie ein Spiel aussehen, aber intern ist es ein wildes Gemetzel von Provisorien...
zatzen hat geschrieben:Ich bin zwar ein paar Jahre jünger als Du aber auch irgendwie schon ein alter Sack, habe nicht mehr wirklich die Energie wie als 15jähriger, dafür aber mehr Geduld. Trotzdem stehe ich in meinem Wissen dort wo ich stehe, und nicht dort wo kommerzielle Spieleentwickler vor 27 Jahren schon standen und einen 386er-tauglichen 60fps Fullscreen 4-Wege Scroller stemmten (Lost Vikings z.B.). Sowas versuche ich lieber erstmal nicht.
Ich bin auch weit davon entfernt, mich mit solchen Spielen zu messen.
zatzen hat geschrieben:Eher mache ich erstmal da weiter, wovon ich seit der Fertigstellung von Kotzman II geträumt habe. Einziger Unterschied: Mit 17 hatte ich dann nen Pentium 200. Das wäre jetzt dann doch zu einfach. Und zu schlunzig, da bräuchte man ja fast kein Assembler, könnte man direkt was in Windows mit mehreren GHz anfangen.
Tja... Wahrscheinlich wäre so'n Pentium 200 ein Rechner, auf dem der lame Kram, den ich so code, endlich mal mit voller Framerate laufen würde...
zatzen hat geschrieben:Ein bisschen Off-Topic:
Ich hatte mal die Idee zu einer automatischen Spieluhr, [...]
Zwar interessant, aber da weiß ich nicht, was ich dazu schreiben soll. Handwerklich bin ich sowas von unbegabt, da kann ich nicht mitreden. Nichtmal, wenn das "Handwerk" aus dem Basteln von Elektronik besteht.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Was derzeit noch fehlt, ist eigentlich nur ein vernünftiger Editor [...]
Ja, bei Kotzman II habe ich instinktiv direkt einen gemacht, mit dem man auch sofort spielen konnte, anders hätte ich die Levels nur schwerlich designen können.
So einen gibt es in Xpyderz - in der (bisher) nicht veröffentlichten Entwicklerversion. Der ist sogar im gleichen Skriptsystem gebaut wie die ganzen Menüs usw. Vielleicht sollte ich diese Version einfach mal raushauen. Im Entwickler kann man noch so einiges mehr machen als der automatische Levelgenerator hergibt. Man kann nämlich in Xpyderz die Blocks auch z.B. verschiebbar oder als Geheimweg machen...
zatzen hat geschrieben:
DOSferatu hat geschrieben:[...]kreativ tätig werden. - geht nicht/schwerer, wenn man wieder technischen Dingen herumkämpfen muß
Ja, das geht dann nur bei mir eher in die Richtung, dass ich mir zuerst gewisse Formate ausdenke, die dann eine technische Basis für das Spiel dienen. Ich habe eben noch nicht so viel Erfahrung und denke nach dem Prinzip "erstmal lieber zusehen dass möglichst viel Daten möglich sind (komprimierte Formate) und wenn's klappt eben diese Beschleunigung durch die Blocktechnik. Für den Geschwindigkeitstest kann ich mir ja das Markieren von Sprites sparen und erstmal willkürlich Blöcke markieren, und dann einfach sehen bis zu welchem Grad teil-Block-kopieren einen Vorteil gegenüber 128000 Byte-Transfer hat.
Naja, ich plane ja auch viel Zeug der Marke "wird vielleicht mal gebraucht". Andererseits habe ich ja auf C64 und PC schon einige Spiele gemacht und weiß - zumindest so ungefähr - was ich gebrauchen könnte. Mir geht's darum, daß, wenn ich erstmal am Spielbauen bin, mir Story, Levels, Figuren ausdenke, nicht mehr andauernd zwischendurch am Programmcode des ganzen "Spiel-Engine"-Zeugs herumfuhrwerken will und mich dadurch ablenke.
zatzen hat geschrieben:Der Amiga war lange Zeit dem PC in einigen Dingen voraus, Spiele hatten am Amiga oft mehr "Multimedia" Features als der PC, nicht zuletzt beim Sound. Erstmal könnte man sagen, gut, für den Amiga waren 500K Standard, die hab ich ja beim PC auch.
Naja, das viel Wichtigere dabei ist: Der Amiga, genau wie der C64 und quasi ALLE Spielkonsolen, haben extra Chips, die Grafik(-Modes), Sprites etc. und Sound produzieren - all das entlastet ja die CPU. Der PC, der ja eigentlich ein Bürogerät ist, hat/hatte das alles nicht - das muß alles durch die CPU, dementsprechend vom Programmierer hingezaubert werden... und dabei möglichst so, daß die ganzen eigentlich "nebenläufigen" Prozesse sich nicht gegenseitig stören.

Deshalb halte ich ja Spieleprogrammierung/-entwicklung für die Königsklasse des Codens/Entwickelns: Es erfordert viele Fähigkeiten auf vielen verschiedenen Gebieten - und die Kunst, diese auch noch gleichzeitig einzusetzen; wobei fast immer eine Ressourcengrenze besteht.

Re: Eigenes Videoformat

Verfasst: So 14. Jun 2020, 11:25
von DOSferatu
Teil 2
zatzen hat geschrieben:Man könnte also das Soundproblem so angehen und sehen dass man alles unkomprimiert in den RAM unterbringt, dann hat man vielleicht 16 Sekunden Sound in "Telefon"-Qualität drin (128000 Byte). Ich möchte da aber etwas mehr mit vergleichbarem Speicher und für Musik vielleicht wenigstens auch ein paar Samples mit 16 kHz. Deswegen das 4 Bit Delta bei ZSM.
Ja, gescheiter Sound kann schon das komplette Spielerlebnis komplett verändern. Aber natürlich muß außer dem Sound noch etwas "Spiel" da sein. Aber Dein ZSM ist da eine ideale Voraussetzung. Ich finde es recht erstaunlich, wie sehr Du die Sounddaten und Sampledaten packst - und auch in diesem gepackten Zustand abspielst und es klingt immer noch super. Meine ISM baut zwar kleineres Zeug (weil meist ja ohne Samples gearbeitet wird), ist aber wahrscheinlich schon vom Code her größer als ZSM und "spart" dadurch nicht wirklich etwas. Und dieser 4bit-Sound (pro Stimme), den das Ding generiert, klingt auch nicht grad nach 2020...
zatzen hat geschrieben:Umgehen könnte ich das Problem wenn ich für die Gravis Ultrasound programmiere, die hat standardmäßig immerhin 256K Speicher. Die ist selber aber kein Standard bzw. hat sich nicht so etabliert wie Soundblaster kompatible, und ich möchte da nicht so modular und mit Extrawürsten programmieren dass man die Wahl hat zwischen CPU-Berechnung und GUS, so dass es bei letzterer schnell laufen würde wie mit AdLib und bei ersterem total leistungshungrig.
Ja, das ist nett. Ich habe z.B. keine GUS. Und ja, beim Sound Blaster muß mehr "manuell" gemacht werden - aber das ist nunmal DER "DOS-Standard"...
zatzen hat geschrieben:Wenn, dann würde ich mit Standards/Etablierten arbeiten und soetwas wie AdLib+One Shot DMA machen. Das wäre dann aber wiederum unterhalb Amiga-Niveau, gewissermaßen.
Naja, One Shot DMA für Klänge hat gegenüber Mixen ja den Nachteil, daß immer nur EIN Soundeffekt gleichzeitig gespielt werden könnte - oder höchstens einer, der durch einen anderen unterbrochen wird. In einem Spiel passieren aber mehrere Dinge, die alle "Geräusche machen" - und da ist es natürlich cooler, wenn diese dann auch gleichzeitig erklingen können.

Ich bin z.B. noch am Überlegen, ob ich Soundeffekte generell als Samples machen sollte, oder ob ich sie aus Klangwellen+Hüllkurven zusammenbasteln sollte (wobei vieles davon dann wirklich SEHR nach 1985 klingen würde...) Ich wollte mir ja immer mal ein Tool schreiben, was Samples "grob" in Folgen von Wellen und Hüllkurven umrechnet... Ich ahne aber, daß die Ergebnisse eher enttäuschend wären: Bei wenigen Daten wird die Ähnlichkeit zum Geräusch gar nicht mehr erkennbar sein. Ab da wo es erkennbar ist, bräuchte man so viele Daten bzw ISM-Befehle, daß das ursprüngliche Sample kleiner wäre... - Aber naja, momentan hab ich auch andere Sorgen...
zatzen hat geschrieben:Also: Um in die 640K einen "Amiga +" unterzubringen brauche ich etwas mehr Leistung, weil ja beim PC alles durch die CPU muss. Ähnlich wohl, wie es erst ab Pentiums auch flüssige Emulatoren für Amiga und Konsorten gab.
Klar. Normalerweise braucht man für einen vernünftigen Emulator mindestens das 100-fache an Leistung des zu emulierenden Systems - weil ja nicht nur die CPU (das ist - auch wenn's komisch klingt - meistens noch das einfachste), sondern vor allem die ganzen speziellen Chips emuliert werden müssen - inklusive allen lustigen Hardwarefehlern, die sie haben und die die Coder dann irgendwann gefunden und ausgenutzt haben...
zatzen hat geschrieben:
DOSferatu hat geschrieben:Mein Problem ist, daß ich mich mit so GUI-Anwendungen ziemlich schwer tue - ich habe für sowas einfach kein Talent.
Für einen Editor den man nur selbst bedient reicht ja eine Shortcut-Bedienung... Meine letzte umfangreichere GUI war die des Frankus Tracker II, da hat mir am wenigsten der Fileselector Spaß gemacht.
Naja, meine Editoren sind schon meistens etwas dröge. Bei dem Level/Sprite-Editor hatte ich eben gedacht, daß man vielleicht mal Leute finden könnte, die zwar nicht programmieren können, aber Lust auf andere Teile der Spieleentwicklung haben (Levels bauen, etc). Nur, wenn sich dann überhaupt einer bereiterklären würde, bei einem Spiel (noch dazu einem DOS-Spiel, an dem nichts zu verdienen ist!) mitzumachen, kann man dem ja keinen hotkeyverseuchten Hex-Editor oder sowas vor die Füße werfen...

Aber inzwischen hab ich's auch aufgegeben, daran zu glauben, es gäbe solche Leute da draußen. Was ich da momentan zusammenschustere, ist wieder eher "freaky"...
zatzen hat geschrieben:Tools baue ich meistens in Freepascal, da nutze ich die 4 GB möglichen Speicher eher schamlos aus, nicht verschwenderisch (sondern dynamisch), aber es ist praktisch wenn man alle benötigten Daten erstmal komplett reinladen kann, in ein Output-Array bearbeiten kann, und dieses dann speichern. 2K gepuffere in DOS habe ich schon länger nicht mehr gemacht.
Tja, ich murks immer noch in blankem DOS und RealMode/V86-Mode herum. Wenn ein Rechner keine 4 GB hat, kann man diese unter Windows ja trotzdem anfordern/benutzen. Da lagert Windows dann viel auf die Festplatte aus (und immer hin und her) - weil vielleicht nicht nur keine 4 GB da sind, sondern weil vielleicht 1. die Grafikkarte einen Teil davon selbst beansprucht und 2. andere Programme und das Betriebssystem selbst ja auch noch Speicher abgraben. - Womit wir wieder beim Festplattenrödeln wären...
zatzen hat geschrieben:Die Zeiten haben sich geändert, in der Diskettenära war es wichtig, die Daten vor allem in den Dateien gepackt zu halten und man hat sie meist in den RAM entpackt weil man sie schnell brauchte. Ich habe 1998, drei Jahre nach der Fertigstellung von Kotzman II, das Spiel nochmal ein bisschen überarbeitet mit Assembler Prozeduren über CALL ABSOLUTE (Basic), mit denen ich statt um die 100 "luftige" Dateien zu laden alles RLE gepackt aus einer .DAT geladen habe. War mir irgendwie ein Bedürfnis.
Ja, diese "EXE + 20 Files" Geschichte war mir auch irgendwann zu blöd und da habe ich eben diese "WAD"-Idee (von id-Software) übernommen - nur mit einem eigenen Filesystem. Es ist nicht perfekt - aber es funktioniert und manchmal reicht das auch.
zatzen hat geschrieben:Evtl. wird das für Leveldaten noch anders sein. Für ein schönes Nostalgiefeeling wäre es toll, wenn mein angedachtes Spiel am Ende auf eine Diskette passen würde und man nichts installieren muss. Ein Diskettenlaufwerk hab ich mittlerweile zu meiner Schande gar nicht mehr, man kann ja heute noch froh sein wenn Computer überhaupt noch ein optisches Laufwerk haben.
"Installieren" muß man bei mir sowieso nie etwas. Einfach auf die Platte legen (für etwas Ordnung am besten in ein Verzeichnis) und fertig. Meine Daten sind nicht so schön gepackt und im Speicher ist da recht wenig gepackt - kommt auf die Daten an. Wenn ich Systeme habe, die auf alle Daten zugreifen können müssen - nicht nur hintereinander fortlaufend, sondern auch irgendwo aus der Mitte, ist natürlich Kompression eher feindlich.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ich habe selten erlebt, daß ich durch HINZUFÜGEN von Code etwas schneller bekomme - eigentlich immer eher vom WEGLASSEN.
Das hört sich natürlich erstmal fatal an, wenn man über die Blocktechnik nachdenkt. Man tauscht zweimal REP MOVSD mit ein paar Befehlen davor durch zig Befehle mit Schleifen ein. Ich habe mal versucht, aus der ASM86FAQ die Takte von REP MOVSD herauszukriegen, ist mir auf die Schnelle nicht ganz klar, sind es auf einem 486er im Realmode 13 + 2n ?
Laut ASM86FAQ sind es 12+3n. Die 12 kann man "ignorieren", die kommen ja nur einmalig, sobald es mehr als 5 sind, ist es mit REP schneller (würde ja sonst keinen Sinn machen, überhaupt REP zu benutzen). Andererseits braucht ein normaler MOV-Befehl von "irgendwo"/Register nach Register oder von Register nach "irgendwo"/Register jeweils nur 1 Zyklus und somit ist man bei 486er mit komplett manueller Geschichte (also alle einzeln kopieren) schneller als mit REP MOVSD! Hier könnte man z.B. zeilenweise arbeiten, oder 4 oder 8 Pixelzeilen nehmen und manuell mit [DI+Konstante] kopieren und dann nur eine 25x-Schleife drum, die jedesmal DI um 2560 erhöht (320*8). So hätte man einen Speicherklotz von 160*8=1280 Befehlen (zwei dword-MOVs pro 4 Pixel) und das bißchen Verlust vom 25-fachen Prüfen von DI (das sind vielleicht insgesamt nicht mal 100 Zyklen)...
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ja, dann hatte ich wohl den Anreiz mit dem 8x8-Block-basierten Ein-Bild-Spiel (ohne Scrollen) nicht ganz verstanden. Ich hatte irgendwie angenommen, daß es darum ginge, die Framerate zu erhöhen, um ein flüssigeres Spiel zu haben[...]
An 70fps oder dergleichen kann ich nur denken, wenn ich ZSM fallen lasse, das Ding schafft in sich schon keine 70 fps, höchstens ohne nennenswerten Grafik/Text-Output. Vielleicht geht mit der Blocktechnik ein 70 oder 35 fps Ding mit AdLib. Commander Keen (die späteren, 4 etc.) hat 35 fps, nebenbei bemerkt. Sollte ja auch reichen. Mein olles Kotzman II läuft aber auf ca. 70 fps, wenn nicht gerade zu viel Feinde kommen. Da war ich meiner Zeit voraus, durch die sagenhafte Selbstlöschung der Sprites...
Naja, es waren ja fast immer nur 1-3 Sprites, die sich da pro Bild bewegt haben und es war nicht mit ständigem Fullscreen-Refresh. Sowas mache ich auch ständig mit Mauspfeilen - dazu brauch ich keinen Doublebuffer - die paar Pixel schafft der auch so, ohne daß dem User komisch wird.
zatzen hat geschrieben:
DOSferatu hat geschrieben:[...]dieses ganze Kollisionszeug ist ja auch nicht gerade ohne[...]
Ich habe mich mit soetwas nie so tief beschäftigt und das Problem "jeder mit jedem" und den immensen fakultativ bedingten Fällen noch nicht so gesehen. Ich glaube bei vielen Spielen sind die Feinde untereinander völlig unbetucht und kollidieren nur jeweils für sich entweder mit der Spielerfigur oder dessen Schuss. So habe ich es bei Kotzman II gelöst und es funktionierte. Also, 20 Feinde, ein Spieler, ein Schuss, 40 Kollisionsabfragen. Es ist lange her, aber ich meine ich hatte einfach für jeden Feindtyp ein Datenfeld und habe dann in Schleifen alles nötige abgefragt und berechnet bzw. Koordinaten verändert. Aber Du wirst einen Grund für die Kollisionsmatrix haben, den ich Mangels Erfahrung nicht sehe.
An "Erfahrung" liegt es weniger - und wenn ich ein Spiel machen würde, wo der Spieler nur einen Schuß gleichzeitig hätte und es eine Handvoll (OK, zwei Hände und zwei Füße voll) Feinde gäbe, würde ich keine aufwendige Kollisionsmatrix einsetzen.

Btw, die Verwendung der Kollisionsbefehle/-matrix ist in GS2 optional. Warum dann also? Naja, ich erkläre es (mal wieder) an Xpyderz:
Xpyderz hat Levels mit Maximalgröße 128x128 Blocks (Block=32x32 Pixel), also 32768x32768 Koordinaten. Man kann maximal 234 große Spinnen (Arachnos) und 500 kleine Spinnen (Spiders) einstellen (und auch glaub max. 100 Geschütztürme) - insgesamt ist das System aber auf über 1500 bewegliche Objekte ausgelegt. Diese Objekte sind auch: Schüsse der Spinnen/Türme, Schüsse des Spielers (der Spieler bei Multiplay). Der Spieler kann nicht nur 1 Schuß abgeben, sondern glaub so 8-10 Schüsse pro Sekunde (je nach Waffenaufbesserung-Bonus), außerdem können diese Schüsse bis zu 5x (auch aufsteigender Bonus) an Wänden reflektieren, bevor sie zerplatzen. Diese Schüsse, sowie auch die Spinnen usw bewegen und agieren im ganzen Spielfeld - auch außerhalb des Sichtfelds des Spielers (wäre bei Multiplay ja sonst blöd), d.h. die Schüsse können auch irgendwo Spinnen treffen, ohne daß man es sieht. 20-100 herumfliegende Schüsse (grobe Schätzung) und 100-500 Spinnen müssen also interagieren. Und da finde ich es schon besser, die "nicht zutreffenden" im Vorfeld grob "auszusortieren.

Natürlich ist es trotzdem aufwendig - aber eben weniger als ohne die Kollisionsmatrix. Was Deine zutreffende Bemerkung angeht, daß die Feinde untereinander "unbetucht" sind... ja, das wird in Xpyderz berücksichtigt - im neuen GameSys2 habe ich das intelligent erweitert: Es gibt 255 Figurentypen - aber jeder Figurentyp kann einen von 16 "Sub-Typen" haben, die seine "Zugehörigkeit" festlegen. Man kann natürlich selbst bestimmen, welcher Subtyp was bedeutet, z.B. Subtyp 0 = Spieler, Subtyp 1 = Bonus, Subtyp 2 = Spielerwaffe, Subtyp 4 = Feind, Subtyp 5 = Feindwaffe...

16 solcher Subtypen sind normalerweise mehr als genug. Und bei der Kollisionsabfrage gibt man, neben anderen Parametern, auch als ersten Parameter ein Bitmuster aus 16bit an. Nur gefundene Subtypen, deren X. Bit im Bitmuster=1 ist, werden weiter geprüft. Bitmuster deshalb, weil man so mehrere Subtypen gleichzeitig prüfen kann - aber eben auch Subtypen weglassen, die keinen Sinn machen, weil sie keine sinnvolle Reaktion ergeben. (z.B. braucht ein Feind nicht die Bonusgegenstände prüfen oder die Spieler- und Feindwaffen sich gegenseitig auch nicht. Gäbe es abschießbare Projektile würde man diese dann eher als Feind deklarieren.

Es ist also nicht so, daß ich diese Kollisionsmatrix nur gebaut habe, weil mir etwa irgendwie langweilig gewesen wäre und ich irgendein Projekt brauchte - das hatte schon alles seinen Sinn.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Also CONST B:byte=5; ist eigentlich eine (veränderliche!) Variable B, nur daß sie gleich mit 5 initialisiert ist.
Wieder was sehr hilfreiches gelernt. Ich dachte bisher immer, Konstanten wären grundsätzlich nicht veränderlich. Code und Mühe sparen, sehr gut. Ich hab mir auch gerade noch eine Frage selbst beantwortet: Konstanten die man zur Array-Definition verwendet müssen fix sein.
Klar, denn die Größe muß ja feststehen. Andererseits kann man Typen bis zu Größen von 64kByte-1 deklarieren. Ich setze z.B. einen Pointer und lege einen Typ maximaler Größe an diesen Pointer. Wieviel ich davon wirklich benutze, hängt davon ab, wieviel Speicher ich für den Pointer reserviere - aber so kann ich kompliziertere Typen (array of record usw.) haben, ohne mich in der Größe festlegen zu müssen. (Ich kann Pointern auch Größen weit über 65528 Bytes zuweisen, die Benutzung erfolgt dann entsprechend mit manueller Verschiebung des Pointersegments.)

(Anm.: Diesen Text hier schreibe ich übrigens gerade auf meinem selbstgebauten Texteditor, der auch "Highlighting" hat (für die QUOTES und für Kursiv/Fett/Unterstrichen) und der über 500000 Zeichen lange Texte (über 6300 Zeilen) speichern kann.)
zatzen hat geschrieben:
DOSferatu hat geschrieben:Wenn man Files in einem Verzeichnis packt (RAR), kann man blöderweise beim Batch-Aufruf von RAR nicht die Option nutzen, die die Oberfläche hat - nämlich: Nach dem Packen alle löschen.
Genau sowas bringt Windows ausnahmsweise von Haus aus mit: Dateien markieren, packen, Markierung bleibt bestehen, Archivdatei ist nicht markiert. Dann Shift+Entf und der Haufen ist weg.
Das hat Du falsch verstanden. Natürlich geht sowas "manuell" unter DOS auch (Stichwort: Norton Commander. Oder mein eigener Imperial Commander.) Ich schrieb: "Batch-Aufruf", damit meinte ich: Tool automatisch aus einem BAT-File starten. In RAR (die Text-GUI) kann man beim Packen so ein [x] setzen für "Dateien nach Packen löschen" - aber als Kommandozeilenparameter hat RAR das nicht. Mir ging es darum, das Ganze zu automatisieren (indem ich z.B. nur eine BAT aufrufen muß) - um eben dieses manuelle Markieren/Löschen nicht machen zu müssen.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Und ein drittes, mit dem ich einen Filenamem ändern kann (bei gleichbleibender Erweiterung) auf den Namen des Verzeichnisses, in dem es liegt.
Könnte ich auch schonmal gebrauchen, aber für Windows eben. Mach ich vielleicht in Freepascal. Ja und Units sollte ich auch mal machen. Ich hab da so jemanden dem ich schonmal irgendwelche Tools und Konvertierer schreibe, wäre nur klug sich dafür mal ordentliche Units zum Lesen von Configs und dergleichen zu machen.
Naja, inzwischen gibt es schon eine ganze Menge Tools von mir, die, wenn unter Windows gestartet, lange Filenamen (LFN) unterstützen. (Das sind nur ein paar Funktionen bei INT $2F.) D.h. ich könnte so ein Tool auch für Windows schreiben, so daß es, wenn es das Vorhandensein der LFN-Funktionen erkennt, dies unterstützt.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ja, wie gesagt: Damit kann man eigentlich nicht realistische Benchmarks machen - DOSbox versucht ja alles so schnell wie möglich abzuarbeiten und haut am Ende nur eine "Kunst-Bremse" rein. Das ist für Benchmarks eher ungeeignet.
Ich habe nur den Eindruck, dass trotz allem Dein PC ungefähr 8x so schnell ist wie die DosBox hier mit 20000 Cycles die sich bei mir eingebürgert haben weil sie den Wirt nicht zu sehr stressen. Und mit denen läuft mein bisheriger Kram flüssig, mit Luft nach oben, das macht mir Hoffnung. Du könntest ja auch mal testen wieviel FPS ein Transfer 64000 Byte Buffer2Buffer2VGA bei Dir schafft, ist ja schnell geschrieben.
Naja, müßte man sehen. Ich bin mir nur nicht sicher, ob die DOSbox die relativen Zyklen der Befehle auch supportet, d.h. wenn ein Befehl B auf der realen Maschine 3x soviele Zyklen wie Befehl A braucht, ob der B dann auch 3x so lange getimed wird.

Bei C64-Emulatoren wird ja immer befehlszyklusgenau emuliert - alles andere macht keinen Sinn, weil's eben unheimlich viel Code für die Kiste gibt, der mit Befehlen getimed ist (bei knapp 1 MHz geht's auch kaum anders), um bestimmte Grafikeffekte ausnutzen zu können.

Bei PCs dagegen war das quasi nie so wirklich ein Thema - da gab es schon zu 8086er-Zeiten diese berühmten "Timerschleifen", die vorher den PC-Speed messen - weil schon zu diesen Zeiten klar war, daß keine zwei PCs gleich schnell sind. (Das Problem war nur, daß die die Reichweiten der Schleifen zu gering eingestellt haben. Auf 'ner schnellen Kiste läuft z.B. so'n 16bit-Register dann schnell mal über/unter und wenn man anschließend durch 0 teilt... naja... wir kennen ja alle unseren lustigen RTE200...) Unter Borland-C heißt der übrigens R6001 (tritt in Monkey Island 1 auf, wenn man nicht genug bremst...)
zatzen hat geschrieben:
DOSferatu hat geschrieben:Gerade wenn die "TUE DAS,WAS GETAN WERDEN MUß" seltener vorkommt - was ja das Ziel ist...[...]
Ja, das muss ich im Zusammenhang dann sehen. Ich hatte angedacht, z.B. horizonal zu unrollen, also 40 Blöcke. Geschwindigkeitsmäßig dürften aber auch 8 Einheiten reichen, und da man das ganze linear und nicht zweidimensional angeht ist es ja egal wo man "trennt", es muss nur am Ende mit 1000 aufgehen.
Ja, die Geschichte, die Spritebreite/-Höhe dann eindimensional (d.h. ohne blöde Multiplikation) zu lösen, wird noch mal etwas "tricky", ist aber machbar.)
zatzen hat geschrieben:
DOSferatu hat geschrieben:XOR AX,BX {und dann die Bits entweder umkehren oder nicht}
Endlich mal ein XOR, NICHT um zu nullen. In die Verlegenheit würde ich auch gern mal kommen.
Naja, ich nutze XOR öfter mal für interessante Dinge: untere 5 Bits von AX soll nach BX und aus AX gelöscht werden (d.h. auf beide Register aufgeteilt):

Code: Alles auswählen

mov BX,AX;and BX,$1F;xor AL,BL
oder

Code: Alles auswählen

mov BX,$1F;and BX,AX;xor AL,BL
zatzen hat geschrieben:Ja, auf jeden Fall sehr gute Methode. Vielleicht fällt mir irgendwann was ein, wie man Werte begrenzen kann ohne Sprünge (also Pseudo: wenn x(signed 16 Bit) > 127 dann x = 127. Und auch das gleiche fürs negativ, bloß dann -128. Scheint mir aber aussichtslos. Ich glaube im Pentiumbefehlssatz gibt es etwas dafür.
Ich denk noch drüber nach. Vielleicht erstmal durch add 32768 in unsigned wandeln.
Es gibt sowas seit 286er, der Befehl heißt BOUND. Such mal in der ASM86FAQ danach. Er löst INT 5 aus, wenn außerhalb. Ja, INT und so. Aber wenn es selten genug vorkommt und die Prüfung nur "zur Sicherheit" ist, könnte das vielleicht Deinem Anliegen helfen/genügen.

Anm.:Es gibt für ab Pentium MMX dann auch noch so "Addition/Subtraktion bis", wo es bei Wraparound auf 0 geht oder auf dem Wert stehen bleint oder auf Maximalwert geht. Aber dieses MMX-Zeug dient ja anderen Zwecken, und Performance spart man damit nur, wenn man a) sich damit auskennt und b) es dafür anwendet, wofür es gedacht ist.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Man könnte auch bei jedem Frame mitzählen. wieviel zu kopieren ist, um dann beim nächsten Frame dynamisch anhand der vorigen Anzahl zu entscheiden, ob man das ganze Bild kopiert oder blockweise.
Ja, wäre ne Option, geht aber dann nur für das Kopieren in den VGA Speicher, der Hintergrund kann ja Redundanzkomprimiert vorliegen. Allerdings wäre es dann ja schon eine Ausnahmesituation, die so nicht vorgesehen ist. Bei variabler Framerate absolut sinnvoll, aber wenn ich das statisch auslege würde an dem Punkt wohl eher das Stottern anfangen, je nach Rechner.
Ja - niemand hat gesagt, daß es einfach wird...
zatzen hat geschrieben:
DOSferatu hat geschrieben:Nur, weil Du es mal erwähntest: Habe zwar auch die TGAME.ZIP da, die nur das "zusammengepackte" TGAME.EXE ist, aber habe jetzt mal das ganze Ding als Einzel-Komponenten hier, damit Du mal siehst, wie das aussieht:
Schön, das wiederzusehen, ich hatte es schon gar nicht mehr so in Erinnerung. Vielleicht hast Du ja auch was geändert. Ich habe jetzt mal 26800 Cycles eingestellt, das entspricht laut DosBoxWiki grob einem 486 mit 66 Mhz. Nicht super-flüssig aber äußerst "spielbar". CTRLBF zeigt 48, alles ein wenig verlangsamt durch die Textdarstellung, aber nicht wesentlich. Du merkst, ich bin gerade etwas Framerate-fokussiert, denn bei der ganzen Block-Geschichte geht es ja nur darum. Beim 2-Ebenen Modus wird es ziemlich langsam, CTRLBF zeigt dann 88.
Ja, wie schon ganz oben erwähnt, dient das nicht einem Performance-Test und die Textausgabe gehört zum schlimmsten Stück Code, das ich jemals gebaut habe. Ich hatte schonmal angedacht, daß in der obersten Bildzeile ein schwarzer Punkt anzeigt, wie voll der Puffer ist, damit man es auch ohne Texteinblendung sehen kann (d.h. je weiter er von der linken Kante weg ist) und das realistischer sieht. Mit selbst ging es da nur um Überlaufstests, sowie den Test, ob der Ringpuffer richtig arbeitet.

Daß die Framerate beim "2-Ebenen-Modus" runtergeht, ist normal: Es ist eine andere Subroutine, die 1 bis 4 unabhängige Ebenen darstellen kann, während ich für den 1-Ebenen-Modus die einfachere 1-Ebenen-Routine benutze. Es wird zwar hier nicht benutzt, ist aber trotzdem eine Option: Die Pixel der Blocks der Ebenen können ebenfalls "halbdurchsichtig" (bzw. in 255 wählbaren Stufen durchsichtig) sein und das auf mehrere Ebenen verteilt.
zatzen hat geschrieben:Ich bin überzeugt, dass Dein System großes Potenzial hat, ich bin nur irritiert wegen der vielen Vollbild-Scroller Spiele mit Bildschirmwiederholraten-FPS aus den Jahren um 1992/93 herum, die eben ihre volle Performance in der Dosbox bei lediglich 20000 Cycles erreichen...
Ja, die Irritierung ist verständlich, wenn man davon ausgeht, daß dies ein Performance-Test-Programm wäre. Aber das ganze Ding ist vollgestopft mit Debugging-Zeug und lahmer Textausgabe, weil eben Test-/Debug-Tool für GS2.
zatzen hat geschrieben:Ich kann erst "mitreden" wenn ich etwas spielbares vorzuweisen habe. Allemal könnte ich mich auf oben erwähnte ZSMPLAY Moonwalker Version stützen, die prima performt hat. Da war zwar nicht viel mit Kollisionsabfrage, aber immerhin konnte man so um die 50 Lemmings laufen lassen für die dann Bedingungen geprüft wurden.
Vielleicht sollte ich endlich mal mit meinem "echten" System ("SLOT") ein Demo-Spiel bauen. Ich nehme an, auch dies wird kaum zu VomHockerHauenEffekten Deinerseits führen, weil eben die volle Schleife mit scrollbarem Level inklusive Sprites, Steuerung und Soundberechnung drin laufen wird. Und selbstverständlich (wie bereits erwähnt) wären "einfachere" Spriteroutinen und "einfachere" Levelroutinen schneller. - Aber genug von Performance.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Da ist auch der "Assembler" drin, der den GameSys2-Source in den Bytecode für die GS2-VM "assembliert" und die Grafiken als PCX.
Interessant und inspirierend, wie Du die Grafiken in dem PCX strukturierst. Naja, gucke mir gern solche Grafikbaustein-Kollektionen an. Man sieht gut, wie Animationen viel Platz belegen.
Naja, eigentlich ist das Ding ein total schlechtes Beispiel, weil ich da viel Platz verschwende. Ich habe es nur so billig gemacht, damit ich 16x16-blockweise sowohl Sprites als auch Levelblocks einlesen kann mit einer billig gebauten Leseroutine. Richtige Spritedaten sähen anders aus. Ich habe immer mal überlegt, ob ich mal ein Sub-Bildformat baue (eigentlich hab ich das schon...), wo man einfach die Sprites in "Kästen" ablegt und quasi "binär" oder mit Pixeln in der Farbnummer oder wasweißich die Image-Nummer angibt. Ein ähnliches Ding existiert schon - da ist an den Rahmen rechts und unten auch noch ein andersfarbiger Pixel - wo die beiden gedachten Linien sich treffen, ist dann der Hotspot des Sprites (wenn nur ein oder kein Punkt, wird der jeweils andere oder beide auf den Mittelpunkt des Images zentriert)... und dann vielleicht zusätzlich noch drüber oder drunter eine 4bit-Palette oder sowas. So könnte man die Sprites in einem normalen komfortablen Malprogramm bauen und dann von einem "Bildparser" einlesen und in nutzbares Format wandeln lassen.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Falls Du Bock hast - wovon ich kaum ausgehe - kannst Du Dir ja die TGAME.GS2 anschauen
Reinsehen gerne, aber zum modifizieren müsste man sich erstmal reinknien und es verstehen.
Naja, es ist quasi wie eine einfache Art "Maschinencode" - auch mit Opcodes und Parametern. Jeder Parameter kann alle Adressierungsarten benutzen, weils im Parameter selbst, nicht im Opcode codiert ist - außer das mit dem "alternativen Offset"... naja, würde zu weit führen, das alles zu erklären. Falls wirklich Interesse besteht, ist ja die GAMESYS2.TXT da. Die ist nur auch schon über 200kByte groß...
zatzen hat geschrieben:Ich werde wohl das nächste Spiel erstmal noch hardcoden. Ich hatte zwar auch schon länger über so eine Game-Engine nachgedacht, diese wollte ich aber nicht durch Instruktionen wie eine VM realisieren, sondern vielmehr durch Definitionen die dann in Arrays landen und von fest programmiertem Code abgearbeitet werden. Vielleicht ein unüberlegter Ansatz.
Du wirst lachen:
1. Ich habe schonmal ein (Textmode-) Spiel gemacht, wo das genau so war.
2. Die Waffen in Xpyderz sind so gemacht - deshalb war es total einfach, neue Waffen mit anderen Fähigkeiten hinzuzufügen.
3. Das ist es, wie GS2 eigentlich benutzt werden soll - da gäbe es dann so Tabellen, aus denen die Werte gezogen werden können je nach Figurentyp - weil Figurenbewegungen sich nicht großartig unterscheiden, soll so ein genereller Ansatz das machen, was sonst für jeden Typ einzeln programmiert werden müßte. Daß es trotzdem so ein umfangreiches System ist, liegt daran, daß auch die Möglichkeit bestehen soll, von den Dingen abzuweichen, um nicht allzu starr auf fixierte Abfolgen festgelegt zu sein. Dadurch kann man etwas "Gegner-Intelligenz" simulieren.
zatzen hat geschrieben:Ich sehe bei Deinem GS2 höchstens den Nachteil, dass es, wenn ich es richtig verstehe, so aussieht, dass die Instruktionen erst als Daten eingelesen werden müssen (kostet Takte fürs Lesen, hardgecodeter ASM würde ja direkt ausgeführt werden) und dann entsprechendes ausgeführt wird. Also gewissermaßen ein Interpreter. Kann mich aber irren.
Nicht nur "gewissermaßen" - es IST ein Interpreter. Es ist eine in 100% Asm geschriebene VM, die quasi diesen Bytecode liest und umsetzt. Vorteil ist eben, daß die Bewegungen (auch die durch die Kollisionsmatrix) und Kollisionen alle schon als abrufbare Befehle ("Opcodes") mit drin sind - schätzungsweise 1/3 der Opcodes ist "spiele-spezifisch" ausgelegt, die restlichen sind so generalisierte Befehle, die es auch in vielen CPUs gibt - um eben Dinge nicht umständlich "umschreiben" zu müssen. Übrigens enthält GS2 auch so einen automatischen Grenzen-Check (d.h. ob ein Wert innerhalb oder außerhalb eines bestimmten Bereichs liegt)... - naja, eben vieles Zeug gleich schon als "fertigen Code", den man für 2D-Spiele wahrscheinlich sowieso braucht.

Und - natürlich ist der Nachteil, daß es nicht so schnell ist wie nativer Maschinencode und ein weiterer Nachteil ist, daß die VM selbst Platz belegt UND der zu lesende Bytecode auch. Aber vielleicht stellen sich im Laufe meiner Spieleentwicklung auch gewisse Vorteile ein.
zatzen hat geschrieben:Du bist natürlich so sehr flexibel, da es ja regelrechte Programmierung und nicht nur Definition ist.Ich habe vor 20 Jahren mal in den Dateien von Duke Nukem 3D gestöbert, und da waren Script-artige Definitionen dabei. Eher selbsterklärend mit Schlüsselwörtern wie "Spawn", z.B. wenn eine Figur zerstört wird und dann herumfliegende Körperteile "gespawnt" werden.
Naja, soweit ich das verstehe, wird das Zeug beim Start von Duke3D irgendwie "eincompiliert", d.h. das wird nicht live während des Spiels die ganze Zeit "als Text geparst" - das wäre ja auch Schwachsinn.
zatzen hat geschrieben:Vielmehr Einblick in nicht 100%ig hardgecodete Sachen habe ich seitdem nicht. Daher dachte ich bei einer einer eigenen Game-Engine wie gesagt nach dem Prinzip, alles in Datenfeldern, und diese müssen nur je nachdem initialisiert werden und dann während des Spiels behandelt. Letztlich wird das nicht viel anders in meinem nächsten Spiel sein, da hat dann eben jedes Level seine Definitionen.
Naja, Spawn-Punkte (also Startpunkte/Einsatzpunkte der Figuren) sind natürlich bei meinem Zeug auch vorhanden - und, weil's keinen "Typ 0" gibt, wird 0 als "Restart-Punkt" benutzt (wenn man im Level verstorben ist, kommt man an diesen Punkt zurück).
zatzen hat geschrieben:Wie auch immer: To do ist jetzt: 1. Block-Dingens Geschwindigkeits-Test programmieren, 2. ZVID2 Converter fertig machen und die Anzeigeroutinen dafür schreiben.
Erinnert mich irgendwie an "Day of the Tentacle":
Bernard:"Also, was soll ich jetzt machen?" - Dr.Fred: "Ich dachte, ich hätte mich klar ausgedrückt: 1. Plane finden. 2. Welt retten. 3. Raus aus meinem Haus. Los gehts!"
Ich finde daran so lustig, daß "Welt retten" so ein zwischendurch genannter Tagesordnungspunkt ist.

******* Ich antworte dann auch gleich mit auf den zweiten Beitrag *******
zatzen hat geschrieben:Zwischenstatus:

Ich habe schonmal angefangen. Konkret werde ich das alles noch anpassen, aber ich habe schonmal ein paar Dinge zu erwähnen. Ich beziehe mich im folgenden auf die Taktangaben für 486er laut ASM86FAQ.TXT:

MOVSD scheint mit 7 Takten ziemlich langsam. Stattdessen verwende ich jeweils "MOV EAX, DS:[SI]; MOV ES:[DI], EAX", was abgesehen von eventuellen Read/Write Penalties der Register dann nur 2 Takte braucht gegenüber 7. Ist Dir wahrscheinlich auch klar, und Du wolltest es in der Demo nur etwas kompakter geschrieben haben.
Ja, habe auch im Text darüber etwas dazu geschrieben.
zatzen hat geschrieben:Ich spare mir das ADD-Hochzählen der Register, indem ich z.B. schreibe ES:[DI+2244] (für das letzte DWORD).
Auch so eine Sache die alles komplizierter als nötig aussehen lässt, und bei MOVSD in der Form ja gar nicht möglich/nötig ist.
Ja, was tut man nicht alles für Speed. An Deiner Stelle hätte ich mir schon lange mal in Pascal so ein kleines Programm gebaut, was Dir eine ganze Latte aufeinanderfolgender Befehle mit anderen Indizes generiert, die Du dann bloß noch in den Code einfügen brauchst. So einen langwierigen (unlooped) Kram würd ich nie von Hand eingeben.
zatzen hat geschrieben:Da ich die unrolled-Variante bevorzuge, [...]von der Seite betrachtet ist es ziemlich optimal.
Ja, wie ich schon sagte: Wenn eine "Ausnahme" selten genug passiert, kann man sogar mit so einem Doppelsprung immer noch Rechenzeit sparen. Aber das hängt eben von Fall zu Fall ab. Bei 20 mittelgroßen Sprites, wenn das Feld schon halb voll ist (d.h. 500 Blocks kopiert werden müssen), geht die ganze Ersparnis dann nach hinten los.
zatzen hat geschrieben:Ich bin mir nicht sicher, ob Deine Technik mit dem 32 Bit Register und Carry hier Vorteile bringt, denn ich muss ja auch implementieren dass alle 40 Blöcke die Kopier-Register um einen speziellen Wert erhöht werden, und auch BX (als Offset des BlockBoolean-Arrays, alle 20 Durchläufe, da ich hier wieder [BX+1] etc. verwende).
Ja, deshalb: Eine innere schnelle Schleife, die das mit dem 32bit-Register und Carry macht und eine langsamere äußere Schleife, die unter anderem die Werte wieder in Ordnung bringt. Auf die Art hat man immer noch den relativen Geschwindigkeitsvorteil, ist aber in seiner Flexibilität weniger eingeschränkt.
zatzen hat geschrieben:Das geht am einfachsten und ohne Testerei des Zählers wenn man eine zweidimensionale Schleife hat, die geschwindigkeitsmäßig hier gar nicht so zu Buche schlägt, da ich die Schleife 20fach entrollt habe, was vielleicht an Wahnsinn grenzt, aber immer noch relativ kompakt aussieht und im Code auch nicht größer als ca. 200 Bytes sein sollte, da JCXZ noch funktioniert.
Ja, quasi wie ich sagte.
zatzen hat geschrieben:Wenn das 20-fach grober Unfug ist kann man es jederzeit auf 10 oder 8 kürzen. Bei so kurz gehaltenem Code war ich mir nur nicht sicher, ob man mit 20-fach das Prefetch nicht besser ausnutzt.
Im Wesentlichen geht es mir hier um Deine Meinung zu "händischem" kopieren über EAX gegenüber MOVSD, und ob so ein [DI+2244]-Konstrukt nicht Geschwindigkeit kostet.
Naja, die Zyklen stehen ja alle im ASM86FAQ mit dabei. Das Ausrechnen aus einem Konstrukt wie [DI+abc] oder [SI+BX+5] (=nennt man das Berechnen der sogenannten "effektiven Adresse") braucht nur auf dem 8086 extra Zyklen je nach verwendeter Methode. Die haben's scheinbar hinbekommen, daß das ab 286er schon alles keine Rolle mehr spielt.

Zur Not hilft aber immer noch: Warten, bis Ticker eins umschlägt, dann das ganze Ding 100000x aufrufen. Und dann Endwert des Tickers*18.2064/100000 und Du hast raus, wieviele solcher Schleifen pro Sekunde er schafft.
zatzen hat geschrieben:Bitte kläre mich auf wenn ich da irgendwo falsch denke.
Naja, so viel gibts auch nicht zu sagen. Wie gesagt: Beste Möglichkeit wäre timen auf 'ner realen Kiste. Ich kenne den Sourcecode der DOSbox nicht - ich kann nicht sagen, ob/wo/wieviele "Speed-Hacks" die eingebaut haben, die zwar schön sind, um alte Software flüssig abzuspielen aber natürlich auch jedes Benchmarking ungewollt sabotiert.
zatzen hat geschrieben:(Ich habe hier zwischenzeitlich viel editiert. Ich denke mal als nächstes mache ich ersteinmal einen neues Post.)
Ja, wird langsam viel. Ich habe es schon in 2 Postings geteilt. Mehr als 60000 Bytes pro Beitrag nimmt das Forum wohl nicht.
Also, weiterhin viel Erfolg!

Re: Eigenes Videoformat

Verfasst: Mo 15. Jun 2020, 23:51
von zatzen
Eine klitzekleine Frage zwischendurch, die wir glaube ich schonmal hatten:
Ich möchte Pascal jetzt z.B. ADD EBX, 0FFFF000Ah vermitteln, via db 66h; add bx, ...
Klappt ja leider nicht, da Konstante außerhalb des zulässigen Bereichs. Gibt es da einen einfachen Weg, oder muss man den ganzen Befehl "inline", also "db 66h, 81h, 0c3h; dd 0ffff000ah" hintippseln?

Re: Eigenes Videoformat

Verfasst: Di 16. Jun 2020, 06:35
von DOSferatu
zatzen hat geschrieben:Eine klitzekleine Frage zwischendurch, die wir glaube ich schonmal hatten:
Ich möchte Pascal jetzt z.B. ADD EBX, 0FFFF000Ah vermitteln, via db 66h; add bx, ...
Klappt ja leider nicht, da Konstante außerhalb des zulässigen Bereichs. Gibt es da einen einfachen Weg, oder muss man den ganzen Befehl "inline", also "db 66h, 81h, 0c3h; dd 0ffff000ah" hintippseln?
Ja, leider geht's nur so. Der Pascal-Assembler versucht ja, intelligent zu sein, und für den ist das ja nur:
* Ein Byte $66, das aus irgendeinem unbekannten Grund da stehen soll.
* Eine Addition einer Byte-Konstante zu einem Word-Register (und dafür gibt es eben extra Befehle, zum Platzsparen)
* Ein Word $FFFF, das aus irgendeinem unbekannten Grund da stehen soll.

Und er nimmt natürlich den besseren (weil kürzeren, und auf langsamen CPUs auch schnelleren, weil weniger Bytes zu lesen) Befehl. Kann man ihm leider nicht ausreden. Und die Byte-Versionen von Befehen können ja - wie Du zweifelsfrei schon bemerkt hast - NICHT in ihre 32-Bit Version gewandelt werden - das geht nur mit den Word-Befehlen. Ich selbst habe da bisher auch noch keine bessere Möglichkeit gefunden, als es komplett inline zu machen.

Anmerkung dazu: Es gibt nicht nur db, sondern auch dw und dd.

Re: Eigenes Videoformat

Verfasst: Sa 20. Jun 2020, 17:44
von zatzen
DOSferatu hat geschrieben:Ich selbst habe da bisher auch noch keine bessere Möglichkeit gefunden, als es komplett inline zu machen.
Ich bediene mich dafür bisher weiter oben genanntem Online Assembler/Disassembler, vielleicht funktioniert der ja auch offline.
Praktischerweise kann man 16 Bit x86 einstellen und trotzdem 32 Bit Befehle (und FS, GS) schreiben, die Overrides werden dann einfach aus der 16 Bit "Perspektive" gesetzt, scheinbar auch Realmode. Hat mir auch geholfen das richtig zu verstehen, ich dachte bisher immer, so ein Prefix wäre nur wegen dem Pascal-Assembler nötig und würde u.U. sogar die Ausführung verlangsamen (je nach CPU ist das ja sogar so).
Ich benutze so einen Asm/Disasm gerne, denn Opcodes selber zu "basteln", anhand der Tabellen, wäre mir etwas zu riskant.

Ich war über die Tage noch mit der Programmierung beschäftigt und da wollte ich das geklärt haben bevor ich unnötigerweise alles "inline" mache, deswegen danke für die schnelle Antwort. Gut zu wissen, dass es nicht anders geht.
ASM86FAQ gibt auch einiges her, wenn man mal nicht nur nach Taktzyklen der Befehle Ausschau hält. Nur eben zu 32 Bit Konstantenübergabe in Pascal-Assembler habe ich dort nichts gefunden - weil es so direkt ja nicht möglich ist.

Ich bin das sehr langsam und überlegt angegangen, habe aber schonmal die simplere "blocks2vga" Routine bis auf weiteres fertig. Mehr Optimierung bekomme ich erstmal nicht hin, und da steckt schon einige Denkzeit drin. Du schreibst, Du liest nicht gerne fremden Code, aber Du kannst ja mal drübersehen, ist ja ziemlich trivial, und die Carry-Geschichten stammen ja von Dir selbst. Vielleicht langweilig für Dich, aber für mich ist es noch relativ spannend, möglichst effektiv programmieren zu lernen. Eigentlich eine so banale Sache, aber bedeutsam, wenn es gut performt. Eigentlich ist es ja mein Kram und ich sollte Dich nicht damit belästigen, aber mir liegt viel dran dass die Performance so gut ist wie nur möglich. Vielleicht siehst Du auf Anhieb noch Möglichkeiten zur Verbesserung.
Noch eine Anmerkung: Ich brauche BX ab 0, daher "getmem(..., 1016); inc(blkinfo_seg); blkinfo_ofs := 0" - ich hoffe das ist so korrekt. Pointer habe ich ABSOLUTE auf die ofs/seg Words deklariert.
Hier also der Code, um Blöcke vom Bildpuffer nach VGA zu kopieren. Ist ein riesen Gerät geworden, aber assembliert immer noch kleiner als ein Blockinfo-Datenfeld.

Code: Alles auswählen

procedure blocks2vga; assembler;
asm { kompiliert: 884 Bytes }
  push bp

  mov ax, 0a000h; dw 0e08eh { mov fs, ax }
  xor di, di

  mov es, scrbuf_seg
  dw 0be66h; dd 00180000h { mov esi, 180000; oberes Word: Durchl. -1 (25 - 1) }
  mov si, scrbuf_ofs

  mov ax, blkinfo_seg; dw 0e88eh { mov gs, ax }

  dw 0bb66h; dd 30000h { mov ebx, 30000h; oberes Word: Durchlaeufe - 1 (4 - 1) }

  jmp @check0

  @copy0: xor bp, bp; jmp @copy
  @copy1: mov bp, 8; jmp @copy
  ...
  @copy9: mov bp, 72; jmp @copy

  @copy_2x_0: xor bp, bp; jmp @copy_2x;
  @copy_2x_2: mov bp, 16; jmp @copy_2x;
  ...
  @copy_2x_8: mov bp, 64; jmp @copy_2x;


  { nach Abarbeitung von @copy0 wird direkt nach @check2 gesprungen }
  { da wenn cl = 0 ist, ch <> 0 sein muss, gleiches für @copy2 usw. }
  @check0: db 65h; mov cx, ds:[bx]; jcxz @copy_2x_0
  test cx, 0fffeh; jp @copy0; jns @copy1
  @check2: db 65h; mov cx, ds:[bx+2]; jcxz @copy_2x_2
  test cx, 0fffeh; jp @copy2; jns @copy3
  ...
  @check8: db 65h; mov cx, ds:[bx+8]; jcxz @copy_2x_8
  test cx, 0fffeh; jp @copy8; jns @copy9

  @10blocksdone:
  db 66h, 81h, 0c3h; dd 0ffff000ah { add ebx, 0ffff0000a } { 1 }
  jc @check0 { 1 / 3 }

  db 66h, 81h, 0c3h; dd 00040000h { add ebx, 40000h } { 1 }

  add di, 2240; { 1 }
  db 66h, 81h, 0c6h; dd 0ffff08c0h { add esi, 0ffff08c0 (08c0h = 2240) } { 1 }
  jc @check0  { 1 / 3 }

  { Zusatztakte pro 40 Bloecke: 4+4+4+2+1+1+1+3= 20 }
  pop bp
  ret


  @copy: { ins Gewicht fallende Sprungtakte bis hier: 5 + 1 Takt mov bp, ? }
    mov cx, cs:[offset @jumptab + bp] { 1 }
    dw 6667h, 6c8dh, 00ddh { lea ebp, [ebp+ebx*8] } { 1 }
    { obere 16 Bit egal }

    db 66h; mov ax, es:[si+bp]; dw 6664h, 0389h { mov fs:[di+bp], eax }
    db 66h; mov ax, es:[si+bp+4]; dw 6664h; db 89h, 43h, 4 { mov fs:[di+bp+4], eax }
    db 66h; mov ax, es:[si+bp+320]; dw 6664h, 8389h, 320 { mov fs:[di+bp+320], eax }
    ...
    db 66h; mov ax, es:[si+bp+2244]; dw 6664h, 8389h, 2244{ mov fs:[di+bp+2244], eax }
    {-> 32 Takte }
    jmp cx { 5 }
    @jumptab:
    dw offset @check2, 0,0,0
    dw offset @check2, 0,0,0
    ...
    dw offset @check8, 0,0,0
    dw offset @10blocksdone, 0,0,0
    dw offset @10blocksdone
    { Mit Hin- und Rücksprung: 13+32 = 45 Takte }

  @copy_2x:
    mov cx, cs:[offset @jumptab + bp] { 1 }
    dw 6667h, 6c8dh, 00ddh { lea ebp, [ebp+ebx*8] } { 1 }
    { obere 16 Bit egal }

    db 66h; mov ax, es:[si+bp]; dw 6664h, 0389h { mov fs:[di+bp], eax }
    ...
    db 66h; mov ax, es:[si+bp+2252]; dw 6664h, 8389h, 2252 { mov fs:[di+bp+2252], eax }

    jmp cx { 5 }
    { Kopiervorgang mit Hin- und Rücksprung inkl. BP-Setting: }
    { 6+2+64+5=13+64=77 }

end;
Die Blockprüfung geschieht 2-Byte-weise. Dadurch können zwei Blöcke, die an geraden Positionen nebeneinander liegen mit einer schnelleren "Doppelroutine" auf einmal kopiert werden. Das wird in der Praxis häufiger vorkommen, bei 2 Blöcken nur bei geradem Info-Offset, ab 3 Blöcke garantiert. Bis zu inkl. JCXZ @... sind es nur 2 Takte, in diesem Fall dann umgerechnet für einen Block nur 1 Takt reine Blockprüfung, plus eben die 77 Takte für die Doppelroutine und den Sprung hin und zurück.
Ich zähle für den ersten Sprung "nach oben" in die weitere Umleitung nur zwei Takte, da ein Jcc bzw. JCXZ bei "branch not taken" einen Takt braucht und sonst zwei mehr, und dieser eine Takt gehört immer zur Blockprüfung und nicht zur Ausführung des Kopierens.

Für den Fall, dass nicht beide Blöcke "TRUE" (= 0) sind, habe ich im Code sozusagen noch zwei Fliegen mit einer Klappe geschlagen, mit TEST CX, 0FFFEh gleichzeitig CL und CH unabhängig voneinander auf 0 geprüft (sind entweder je FF oder 0). Mittels Parity (mal ne sinnvolle Nutzung - man sagt diesem Flag ja keine besondere Nützlichkeit nach) und Sign. Ich hätte stilistisch mit dem Zero Flag auch noch eine dritte Fliege dabei, für den Fall beide sind Null, hätte dann nur TEST GS:[BX+?], REG machen müssen (2 Takte), lediglich gefolgt von drei Jcc's, was aber die Performance unterm Strich verschlechtert und trotzdem ein (dazu auch noch beständiges) Register gebraucht hätte. Achja, und endlich habe ich mal eine sinnvolle Bekanntschaft mit LEA gemacht.

Pro zwei geprüfte Blöcke werden für den Fall "nichts kopieren" 5 Takte verbraucht, also auf einen Block umgerechnet 2,5 Takte. Für die Schleife und diverses Resetten/Updaten außen herum fallen 20 Takte pro 40 Blöcke an, das sind 0,5 pro Block, also kann man sagen im "Worst Case" 3 Takte pro Blockprüfung. Es ist also nicht vermessen zu sagen, dass ich für das Testen von 1000 Blöcken nicht mehr als 3000 Takte brauche. Mit 3000 Takten schafft REP MOVSD 4000 Byte, diese Menge verliere ich an Durchsatz. Wenn man annimmt, dass ein Block-Kopiervorgang 48 Takte braucht (wie bei REP MOVSD), würden 60000 Byte Durchsatz übrig bleiben mit gleicher Geschwindigkeit wie mit REP MOVSD. Da die Kopierroutinen hier aber weniger Takte als vergleichbare REP MOVSD benötigen und auch die Blockprüfung bei höherem Kopieraufkommen weniger Takte braucht, könnte man theoretisch mehr Durchsatz erreichen. Also bei 60000 Bytes Durchsatz eher noch schneller als mit REP MOVSD - rein theoretisch. Noch ein Gedanke: Voller Screen-Update: 1000 * 1,5 Takte Pro Blockprüfung, 500 * 77 Takte Doppelblock-Kopieren --> 1500 + 38500 = 40000 Takte. REP MOVSD schafft damit nur 53332 Bytes... Wiederum, Theorie... Also wenn jemand so faul ist, mit REP MOVSD zu kopieren anstatt das etwas weitläufiger zu unrollen, dann ist meine Blockroutine - theoretisch - in jedem Fall schneller...
- Okay, ich habe es getestet. Ein bisschen ernüchternd, aber noch brauchbar: Bis etwa 500 Blöcke zieht meine Routine mit REP MOVSD gleich. Dosbox ist allerdings kein Anhaltspunkt, und in diesem Fall zu meinem Vorteil. Dosbox emuliert REP MOVS? offenbar zu schnell, und meine Theorien sind durchaus haltbar.
Ich ahne da etwas, dass Dosbox generalisiert quasi einen Pentium emuliert. Und bei dem braucht REP MOVSD nur 1n.
Ich habe mal getestet: Unrolled (EAX beladen, Speicher mit EAX befüllen (stupide nur "auf einer Stelle", dürfte genügen als Simulation, oder?), je 80 mal und in Schleife 200x) vs. REP MOVSD für 64000 Bytes. Ergebnis: Unrolled braucht doppelt so lange. Gleiches passiert wenn ich einfach nur 16000x Register schaufle, ohne Speicherzugriff. Daher kann man Prefetch-Penalties auch ausschliessen, d.h. sollte Dosbox die emulieren. DosBox behandelt REP MOVSD offenbar mit 1n, egal welcher Prozessortyp und Core eingestellt ist. Auf richtiger Hardware, wo REP MOVSD dann tatsächlich 3n "hat", könnten meine Theorien dann ziemlich gut hinkommen. Allemal besser als sich von einem 3-fach so schnellen REP... täuschen zu lassen, so dass das Ergebnis hinterher nur auf Pentiums läuft.


Es gibt nach meinem Geschmack jeweils einen Sprung zu viel. Und JMP CX ist auch etwas langsam. Aber irgendwoher müssen die Kopierroutinen ja die Koordinaten und die Rücksprungadresse beziehen können, und ich wollte das BP-Setting nicht in die Blockprüfung einbauen.
Ich kann den Einfluss des Prefetchings übrigens noch nicht so recht einschätzen und kenne mich damit zu wenig aus. Deswegen hauptsächlich nur die Einschätzung durch Takte-zählen.

Ich musste die Adressierung "mov fs:[di+bp+?]" inline schreiben, der Pascal Compiler packt bei Benutzung vom Override $64 und BP ein $3E (also DS Override) mit rein, wodurch der $64 Override dann nicht mehr gilt. Mag er anscheinend nicht...

Also soviel dazu. Mir liegt viel daran, hier möglichst viel Performance zu haben, sorry für so viel Brimborium, wir haben sowieso schon viel drüber diskutiert. Aber es scheint sich zu lohnen.

Aber noch etwas: Bevor ich das mit den (quasi nicht korrekt emulierten) 1n festgestellt hatte wollte ich es dann doch wissen und habe mir die Mühe gemacht, eine komplett unrolled Variante zu schreiben (dabei auch ein kleines Programm, das den unrolled Code produziert, wie Du empfahlst). Kopierroutinen, Blockprüfung und Schleifen verschachtelt und repitiert. Letztlich war diese Routine aber auch nicht schneller als die bisherige. Zum Glück, denn sonst hätte ich jetzt die Qual der Wahl, ob ich eine wohl > 3K Routine für soetwas simples verwenden soll. Und als restoreblocks-Variante wäre das noch viel aufgeblasener geworden.
Aber es hat sich gelohnt, habe wieder etwas gelernt: Befehle wie MOV, LEA und Sprünge lassen die Flags in Ruhe. War mir bisher nicht so klar dass man sowas wie TEST AX, 0FFFEH machen kann, über JP irgendwo hinspringen, dort rumschaufeln was das Zeug hält, und dann als nächstes ganz einfach nochmal mit JNS woanders hinspringen kann. Sehr Takte-sparend.
Eine Frage zu LEA: Ich hätte zwischendrin mal LEA EBP, [EBX*8+0] gebraucht. Scheinbar geht das nicht aus dem 16 Bit Modus heraus.

Abschliessender Test: 16000x EBX nach EAX und EAX nach EBX kopieren (80x unrolled) vs. Blockroutine: Blockroutine ist bei < 1000 Blocks immer schneller und bei 1000 Blocks etwa gleich auf. Also wenn man es so betrachtet - optimal.

Ich mache dann mal einen neuen Post zur Beantwortung Deiner beiden anderen vorherigen Nachrichten.

Re: Eigenes Videoformat

Verfasst: So 21. Jun 2020, 00:45
von zatzen
DOSferatu hat geschrieben:Ich mache es in meinen Games genauso: Eine Hauptschleife, in der nacheinander "immer im Kreis" die ganzen Dinge ausgeführt werden, wenn das "Flag" (bzw der Zähler) drüber ist. Aber in diesem Fall ist die Framerate NICHT KONSTANT[...]
Ich könnte mir das bei mir eher nur so vorstellen, dass ich eine Basis-Framerate habe von der zeitlichen Länge eines Soundpuffers, und dass dann evtl. die Framerate für die Grafik entsprechend halbiert etc. wird, bei Performanceeinbruch. Aber lass mich erstmal ein Spiel auf meine Weise versuchen. Das Block-Dingen ist nicht zuletzt aus dem Problem heraus entstanden, dass ich die Framerate erstmal fix gestalten möchte.
DOSferatu hat geschrieben:Ein Mißverständnis. Ich warte in TESTGAME nicht auf Retrace, soweit ich mich erinnere.
Ich meinte die Blöcke-Demo, da ist die Framerate mit auf Retrace warten "gerastert" und bricht dann sofort erstmal auf die Hälfte ein wenn's nicht mehr reicht. Sowas in etwa könnte ich mir noch vorstellen selber zu realisieren in einem Spiel.
DOSferatu hat geschrieben:Man muß es ja leider 2-stufig machen: Erst die komplette (Wieder-)herstellung ALLER Bereiche, die von Sprites belegt sind oder im vorigen Frame waren und dann alle Sprites zeichnen. Ja, das klingt wie doppelte Arbeit - wieso kann man nicht gleich die Blockwiederherstellung und das Sprite-Zeichnen für jedes Sprite direkt nacheinander machen, wenn man schonmal die Werte hat? Weil man damit überdeckte (bereits in diesem Frame gepixelte) Sprites wieder teilweise oder vollständig löschen würde.
Wenn man Blockwiederherstellung und Sprite-Zeichnen für jedes Sprite direkt nacheinander machen würde würde das aber auch je nachdem zu mehr Blocktransfers führen, weil bei Überlappung ja gleiche Blöcke markiert werden. Finde ich also schon in Ordnung so.
Woran ich vorab schonmal denke: Ich möchte Hintergrundblöcke mit Transparenzmöglichkeit haben, damit ich z.B. eine Hintergrundebene habe und eine Vordergrundebene. Dazu muss es entsprechende Blockroutinen geben. Vielleicht ist hier eine unrolled Variante am besten, wenigstens 8x, und vielleicht kann man ja noch eine andere Lösung finden als sich Byteweise durch die Register zu prüfen. Bei der Blockroutine konnte ich ja mit test reg, 0FFFEh direkt low und high gleichzeitig testen, aber da wären dann auch wieder jumps. Vielleicht ist es auch möglich ohne Sprünge Zielregister weiterzudrehen bei transparenter Farbe. Muss ich mal drüber nachdenken. Hmm, "cmp al, 1; adc bp, 0" und dann eben mit "al, mov ds:[si+bp]" und "mov es:[di+bp], al".
Moment, mal konkret als Code formulieren:

Code: Alles auswählen

  xor bp, bp

  mov al, ds:[si+bp]
  cmp al, 1
  adc bp, 1
  mov al, ds:[si+bp-1]
  mov es:[di+bp-1], al

{ gewöhnlich: }
  mov al, ds:[si+bp]
  test al, al
  jz @skip
  mov es:[di+bp], al
  @skip:
  inc bp {bp kann bei unrollen wegfallen und durch + ? ersetzt werden }
  
{ gewöhnlich schneller: ]
  mov cx, ds:[si+bp]
  jcxz @skip2
  test cl, cl
  jz @skip
  mov es:[di+bp], cl
  @skip:
  test ch, ch
  @jz @skip2:
  mov es:[di+bp+1], ch
  @skip2:
  add bp, 2 {bp kann bei unrollen wegfallen und durch + ? ersetzt werden }

Gewöhnlich ist also gleich lang wenn nicht gesprungen wird, sonst einen Takt länger und es wird eben gesprungen. Und "gewöhnlich schneller" ist entweder (teils deutlich) schneller oder gleich, beide gewöhnlichen Varianten sind ohne BP Nutzung entsprechend unrolled schneller. Und einen Haken hat die Version ohne Sprünge: Wenn das letzte Element transparent ist wird danach noch ein unerwünschtes Byte an höchster Adresse + 1 kopiert. Kann man vielleicht durch unrolling und dann anderes Ende lösen, aber irgendwie gefällt mir das ganze auch nicht so richtig, auch wegen dem 2x einlesen. Zudem ist das Hochzählen von BP unberechenbar und somit kann man das ganze nicht unrolled strukturieren. Naja, meine ersten Gehversuche mit nicht ganz so trivialem Code... Aber es macht Spaß über kleinen Sachen länger zu brüten, wenn man dann eine kompakte Lösung findet.
Der Gedanke hier war, ich möchte in den Blockdaten die 64 Bytes nicht überschreiten, sonst haut es am Ende mit der Adressierung nicht hin. Deswegen kann ich die Daten schlecht RLE-mäßig anlegen, d.h. Info-Byte wieviel transparente Blöcke kommen, dann Info-Byte wieviel nicht-transparente usw. im Wechsel, das könnte alles ziemlich aufblasen je nachdem, auf maximal ca. 96 Bytes. Wobei das schon geht, wenn das Bild ansonsten einige Blöcke beinhaltet, die einfarbig oder komplett transparent sind.
DOSferatu hat geschrieben:[Blockfummelroutine vs. REP MOVSD]Nicht nur deshalb - sondern weil natürlich eine durchgehende Routine schneller ist als eine, die jedesmal einzeln zur Blockposition muß, also diese vorberechnen und mit Einzelbefehlen einen Kasten füllen.
Das ist mir bisher noch etwas unklar, rein Taktmäßig sagtest Du (und ASM86FAQ) REP MOVSD braucht 3n Takte. Einzelbefehle (2x MOV 32 Bit) brauchen aber vergleichsweise nur 2n Takte. Die Berechnung der Position erfolgt bei meiner Routine in einem einzigen LEA-Befehl, abgesehen davon dass gesprungen werden muss und BP ein Wert zugeordnet werden muss, und ein Takt für das einlesen des Rücksprungs draufgeht - mehr aber nicht.
zatzen hat geschrieben:Und klar, dass 1000x BlockGefummel, selbst in bestem Assembler, langsamer ist als 64000 Byte per REP MOVSD.
Um mich selbst zu zitieren... Ich bin da jetzt verwirrt. Es heisst 3n, aber in Dosbox sieht das mehr nach 1n aus. Vielleicht hast Du Klarheit auf einer realen Maschine.
DOSferatu hat geschrieben:Ja, meiner Einschätzung nach lohnt es sich wirklich maximal bei einer einstelligen Anzahl (1-9) "kleiner" (max. 32x32) Sprites. Bei allem, was darüber ist, wird man meiner unmaßgeblichen Meinung nach kaum noch Performance rausholen, sondern eher mit komplizierten Routinen herumhadern, die am Ende gar nicht wirklich helfen.
Der Test hat gezeigt dass die Blockroutinen etwa gleich schnell (bei 1000 Blöcken) und schneller (bei weniger) waren, als das ganze entsprechend mit unrolled 32 Bit MOVs. Hatte ich ja schon erwähnt. Daher, wenn es langsam ist, liegt es an den MOVs, weniger an der Blockprüfung und der Koordinaten"berechnung".
DOSferatu hat geschrieben:Mit "Mode-X" bezeichne ich zusammenfassend alle Modi, die durch das Unchaining (und die damit verbundene komische *4-Adressierung) die vollen 256kByte VGA-RAM benutzen können. Daß damit auch 320x240 geht, ist nur der Nebeneffekt, weil man dann keinen "Wraparound" mehr hat, wenn man den einstellt. Anders ausgedrückt: Den "Mode-X" gibt es auch für 320x200 - da hat man dann 4 "Bildschirmseiten" - bei 320x240 hat man 3, usw.: Anzahl_Seiten=262144 div (Breite*Höhe).
Ja, ich dachte nur, wenn ich mir schon die Mühe mache, dann will ich auch quadratische Pixel, was gerade bei Emulation sinnvoll ist, weil nicht jeder "dumme User" da einstellt dass der Aspect richtig dargestellt werden soll.

Ja und mit Unabhängigkeit der ganzen Prozesse... Ich begreife es ganz langsam wie gesagt. Aber ich versuchs erstmal so nach meiner Idee, irgendwie muss man ja auch aus eigenem Antrieb vorankommen.
DOSferatu hat geschrieben:Anm.: Falls Dir "Blättern" gefallen sollte: Mein C64-Spiel "Rockus" scrollt z.B. auch nicht - da wird auch geblättert: 16 Bildschirmseiten pro Level, 27 Levels (9 Hauptlevel à 3 Unterlevel) - und es ist ein Jump'n'run - und ja, sogar mit Musik.
Ich hab mir das letztens nochmal angesehen (ja, auf Youtube). Das ist ja gigantisch. Richtig schöne Grafik und schön klingende Musik, bei der irgendwie lustig ist, dass es alles bekannte Stücke sind, die Du irgendwie etwas anders umgesetzt hast als die Originale, aber man erkennt sie trotzdem. Allein dass so viele Feinde wie vom Himmel rieseln ist etwas ungewohnt für mich. Aber an dem Ding kann man gut Dein Potential sehen (wenn Du soetwas auf dem C64 gemacht hast, was kann man dann auf dem PC erwarten...).
DOSferatu hat geschrieben:Naja, ich liebe ja dieses Chiptune-Zeug. Für mich ist das ja nicht nur "aus der Not heraus" (Speichermangel/Performance usw.), sondern, weil ich es wirklich so haben will.
Ja den Sound willst Du so. Für mich war es gleichbedeutend auch die Faszination, mit möglichst wenig Daten etwas vernünftig klingendes zu zaubern. Daher habe ich mich gesträubt eine 8K Tabelle mit reinzunehmen, wäre ja doof, das immer im Hinterkopf, dann kann man sich nicht mehr an ZSMs < 1K freuen. Aber für ein Spiel wäre das egal, da werde ich Musik+FX bestimmt mehr als 100K haben.
DOSferatu hat geschrieben:[...]In diesem Fall hätte man 2000 Bytes für Blockzeiger und 64000 Bytes Bilddaten - also zusammen 66000 Bytes - was leider gerade ein bißchen (464 Bytes) mehr als ein volles 64k-Segment wäre, so daß die Routinen dafür wieder etwas komplizierter würden als nötig, wegen der möglichen Segmentüberschreitung. (Eine andere Lösung ist dann die 2000 Zeiger und die bis zu 64000 Daten an 2 verschiedene Segmete zu legen, aber so viele Segmentregister hat der x86 nunmal nicht und die kann man woanders besser gebrauchen.
Wobei ich für die block2vga Routine mit Absicht DS nicht verwendet habe, damit ich sehe dass es auch ohne dieses funktioniert, und ich es dann in der restoreblocks-Routine verwenden kann. Ich mache mir manchmal eher Sorgen um die Adressregister, so viele sinds ja auch nicht, es sei denn 32 Bit-mäßig kommen noch EAX, ECX und EDX dazu. BP nutz ich gern und oft, SP lässt man aber wohl lieber in Ruhe... Lokale Variable vermeide ich zudem lieber (da würde wohl BP benötigt), schon aus Geschwindigkeitsgründen. PUSH ist ja noch flott, aber POP braucht 4 Takte...
DOSferatu hat geschrieben:Ab 286er erhöht die Anzahl Schiebungen/Rotationen nicht mehr die Zyklen.
Nur um 1 shiften ist langsamer als mit mehr. Aber dafür gibt's ja ADD. Schade dass es kein solches Pendant für SHR 1 gibt.
DOSferatu hat geschrieben:RAM-Zugriffe hängen z.B. von der Zugriffsgeschwindigkeit der RAM-Bausteine ab - bei (langsamem) Grafikkarten-RAM noch deutlicher. Und immer, wenn die CPU einen RAM-Zugriff macht, muß sie auf den RAM warten.
Das ist mir geläufig, und so würde es einem auch gelehrt wenn man sich einen neuen PC zusammenstellen wollte. Bloß, man liest ja in ASM86FAQ eindeutige Wert für RAM-Zugriffe, bei 486ern meist nur 1 Takt. So dass man geneigt sein könnte, die einstige Verkündung ("Assembler ist schnell weil man viel mehr nur mit Registern rechnet") über den Haufen zu werfen und einfach wenn die Register ausgehen ohne schlechtes Gewissen Speicherstellen zu benutzen.
DOSferatu hat geschrieben:Ich hatte immer gedacht, das Z stünde für zatzen. (Was natürlich die nächste Frage aufwirft, nämlich, wofür zatzen eigentlich steht...)
Ja, steht für Zatzen, das bin ja ich. Früher hatte ich noch einen "Firmennamen", "Frankus GHBM". Frankus GmbH war ein sponaner Einfall, aber da dachte ich, nee, ich bin ja gar keine GmbH. Also einfach die Buchstaben verdreht. So ist man als Grünschnabel. Zatzen kam dann so als ich 14 war, ich hatte einen Mitschüler der die heftigste Wortakrobatik betrieb wenn es darum ging, aus den Namen der anderen (und deren Nachnamen) bescheuerte Konstrukte zu bilden. Zatzen ist nur ein Ausschnitt aus solch einem 16-silbigen Konstrukt, ich fand es lustig weil jemand das daraus rausgriff und drüber lachte, seitdem heisse ich so als Spitzname (u.a.). Das wird A übrigens lang gesprochen.
DOSferatu hat geschrieben:[automatische Spieluhr]Zwar interessant, aber da weiß ich nicht, was ich dazu schreiben soll. Handwerklich bin ich sowas von unbegabt, da kann ich nicht mitreden. Nichtmal, wenn das "Handwerk" aus dem Basteln von Elektronik besteht.
Das sollte nur verdeutlichen, dass ich sozusagen die "Interna" von etwas genießen kann, auch wenn der Endbenutzer diese nie zu Gesicht bekommt und sich nicht dafür interessiert. Aber sollte ja klar sein. So programmiere ich auch nicht nur, weil ich unbedingt dieses erdachte Spiel haben will, sondern das Programmieren selbst und die ganzen krummen Formate sind für mich ebenbürtig.
DOSferatu hat geschrieben:Man kann nämlich in Xpyderz die Blocks auch z.B. verschiebbar oder als Geheimweg machen...
Das erinnert mich jetzt an Zelda... (NES)
DOSferatu hat geschrieben:Naja, ich plane ja auch viel Zeug der Marke "wird vielleicht mal gebraucht".
Bisher sind es essentielle Dinge im Zusammenhang meiner dürftigen Ideen. ZSM für Sound, ZVID2 für Sprites, Blocktechnik für Performance und Hintergrund"kompression". Mehr ist da eigentlich nicht in Arbeit, aber ich programmiere ja auch weniger als Du.
DOSferatu hat geschrieben:Und dieser 4bit-Sound (pro Stimme), den das Ding generiert, klingt auch nicht grad nach 2020...
Du möchtest/kannst daran wahrscheinlich nichts ändern. Ansonsten ist damit ja nichts verkehrt, der Sound von 8 Bit Systemen ist bloß normalerweise überaus sauber (je nach Sound), wie ich schon sagte, man braucht CD-Eckdaten um den Klang eines NES oder C64 unverfälscht einzufangen.
DOSferatu hat geschrieben:Nur, wenn sich dann überhaupt einer bereiterklären würde, bei einem Spiel (noch dazu einem DOS-Spiel, an dem nichts zu verdienen ist!) mitzumachen, kann man dem ja keinen hotkeyverseuchten Hex-Editor oder sowas vor die Füße werfen...
Den meisten müsstest Du wohl einen Editor mit Windows-GUI anbieten...
DOSferatu hat geschrieben:Andererseits braucht ein normaler MOV-Befehl von "irgendwo"/Register nach Register oder von Register nach "irgendwo"/Register jeweils nur 1 Zyklus und somit ist man bei 486er mit komplett manueller Geschichte (also alle einzeln kopieren) schneller als mit REP MOVSD!
Genau das wollte ich hören (bzw. lesen)! Dann wäre die Sache ja klar, der Fehler liegt bei DosBox, und meine Routine performt sehr gut, quasi nach Theorie.
DOSferatu hat geschrieben:(Anm.: Diesen Text hier schreibe ich übrigens gerade auf meinem selbstgebauten Texteditor, der auch "Highlighting" hat (für die QUOTES und für Kursiv/Fett/Unterstrichen) und der über 500000 Zeichen lange Texte (über 6300 Zeilen) speichern kann.)
Ich mach das innerhalb Windows mit dem simplen Editor und muss alles kursive etc. selbst einstellen. Hab ich in diesem Quote mal sein gelassen. Wäre natürlich praktisch, aber ich mache nunmal den Hauptanteil meiner Computerarbeit in Windows. Irgendwann hat es sich so ergeben, ich glaube seit XP. Evtl. könnte ich mir sowas in Freepascal bauen. Dann mit 4000000000 Zeichen langen Texten...
DOSferatu hat geschrieben:[...]wenn unter Windows gestartet[...]
Geht ja leider seit den 64 Bit nicht mehr...
DOSferatu hat geschrieben:Es gibt sowas seit 286er, der Befehl heißt BOUND. Such mal in der ASM86FAQ danach. Er löst INT 5 aus, wenn außerhalb. Ja, INT und so. Aber wenn es selten genug vorkommt und die Prüfung nur "zur Sicherheit" ist, könnte das vielleicht Deinem Anliegen helfen/genügen.
Leider ist es das "Clipping" des Soundpuffers, um ihn von 16 Bit sicher in 8 Bit zu überführen. Kommt sooft vor wie die Samplefrequenz ist.
Ich habe da bislang:

Code: Alles auswählen

{ ax ist signed integer, kann < -128 und  > 127 sein, muss aber auf 0-255 (unsigned) begrenzt und konvertiert werden }
add ax, 128
js @negclip
or ah, ah
jz @noclip_skip
mov ax, 255
jmp @noclip_skip
@negclip:
xor ax, ax
@noclip_skip:

( mov es:[di], al )
Das ist jetzt gefühlt Urzeiten her, und ist wohl noch verbesserungswürdig. Hab es nur mal schnell hervorgekramt.
DOSferatu hat geschrieben:Ja, was tut man nicht alles für Speed. An Deiner Stelle hätte ich mir schon lange mal in Pascal so ein kleines Programm gebaut, was Dir eine ganze Latte aufeinanderfolgender Befehle mit anderen Indizes generiert, die Du dann bloß noch in den Code einfügen brauchst. So einen langwierigen (unlooped) Kram würd ich nie von Hand eingeben.
Mir kam da auch so die Idee, ob man sich selber nicht auch einen kleinen Assembler schreiben könnte, der einen Code durchscannt und nur die für den Pascal Assembler unbekannten Sachen ver"inlinert". So könnte man munter drauf los legen mit 32 Bit Befehlen und müsste nicht ständig externe Assembler bemühen und die Hexfolgen abtippen. Wäre in einer Windows-Umgebung sehr praktisch, dort könnte man den Code Programmübergreifend einfach über Copy+Paste transferieren. Dosbox ist da leider abgeschirmt. Jedenfalls wäre das dann so als hätte Pascal in einem Popup sowas drin, wo man den Code reinschreiben kann und er wird gewandelt. Mal sehen.


Ich habe jetzt hier nicht so viel geschrieben. Ich habe aber alles aufmerksam gelesen, möchte Dich aber auch nicht übermäßig beanspruchen und von Deinen eigenen Sachen abhalten. Mit der Blocksache hast Du mir schon ziemlich viel Zeit gewidmet.

Re: Eigenes Videoformat

Verfasst: Mo 22. Jun 2020, 22:55
von zatzen
Hey, ich wollte nur schnell vermelden, dass ich auf die Idee gekommen bin dass man bei der Blockprüfung ja auch direkt ein 32 Bit-Register beladen kann, und so wiederum Takte sparen. Wenn Dir spontan irgendwas einfällt zu dem Code im vor-vorherigen Post kannst Du das gerne äußern, ansonsten merke ich, dass mir immer noch was einfällt wenn ich mich in Ruhe damit beschäftige. Ist alles noch nicht spruchreif.
EDIT: Ich sehe gerade leider, dass das gar nichts bringt. Aus einem 32 Bit Register kann man in dem Zusammenhang hier auch nur zwei Conditions ableiten, bzw. funktioniert "test eax, 0fffe0000h" gar nicht, da Parity nur für das niederwertigste Byte ausgewertet wird.
So läuft es also aus meiner derzeitigen Sicht auf

Code: Alles auswählen

@check0: mov ax, gs:[bx+?]; test ax, 0fffeh; jnp @copy0
@check1: js @copy1
...
hinaus, die TRUE-Markierungen sind dann wieder jeweils $FF, da ich jcxz nicht mehr verwende.
Man kann das großzügig unrollen (z.B. auf 40 check's/copy?'s), das spart eine Schleife) und hat dann am Ende kaum mehr als 2 Takte pro Blockprüfung. Alternativ zu "test ax, 0fffeh" wäre "add ax, ax", wirft in diesem Fall die gleichen Flags und noch Carry, und es sind nur zwei Byte Opcode.
Vielleicht fällt mir noch etwas ein. Ich habe da ersteinmal keine Eile.

Re: Eigenes Videoformat

Verfasst: Sa 4. Jul 2020, 14:41
von DOSferatu
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ich selbst habe da bisher auch noch keine bessere Möglichkeit gefunden, als es komplett inline zu machen.
Ich bediene mich dafür bisher weiter oben genanntem Online Assembler/Disassembler, vielleicht funktioniert der ja auch offline.
Ich kenne diese Online-Assembler, benutze gelegentlich auch mal einen davon. Und: nein, die funktionieren nicht offline.
zatzen hat geschrieben:Praktischerweise kann man 16 Bit x86 einstellen und trotzdem 32 Bit Befehle (und FS, GS) schreiben, die Overrides werden dann einfach aus der 16 Bit "Perspektive" gesetzt, scheinbar auch Realmode.
Natürlich - alles andere würde ja keinen Sinn machen.
zatzen hat geschrieben:Hat mir auch geholfen das richtig zu verstehen, ich dachte bisher immer, so ein Prefix wäre nur wegen dem Pascal-Assembler nötig und würde u.U. sogar die Ausführung verlangsamen (je nach CPU ist das ja sogar so).
Eigentich nicht. Denn die CPUs, wo das so wäre, können sowieso keinen 32-Bit Code ausführen. Also, dieser Präfix ist kein irgendwie gearteter Hochsprachen-Kram, sondern gehört zum Befehlssatz der CPUs ab 386er und schaltet jeweils die Registerbreite ($66) oder Adreßbreite ($67) um. Wenn man im 16-Bit-Mode ist, auf 32-Bit. Wenn man im 32-Bit-Mode ist, auf 16-Bit. - Immer jeweils für den folgenden Befehl. Da muß man auch drauf achten, daß der folgende Befehl damit überhaupt etwas anfangen kann, sonst kann es (je nach CPU) passieren, daß es dann für den übernächsten Befehl genommen wird!
zatzen hat geschrieben:Ich benutze so einen Asm/Disasm gerne, denn Opcodes selber zu "basteln", anhand der Tabellen, wäre mir etwas zu riskant.
Ich plane schon länger, mal einen Inline-Assembler direkt für Pascal-Sourcen zu coden, der alle Befehle assembliert, aber nur, wenn er erkennt, daß es >=386er Befehle sind, diese dann "inlined" (als Bytes/Words/DWords) hinschreibt und dahinter auskommentiert {...} den vorher eingegebenen ("echten") Befehl. Er würde dann auch so Dinge, die man selbst schon "verschönert" hat (mit db $66) NICHT wandeln, weil ja das, was dahintersteht, auch normaler 8086/286-Code wäre.

Ich kenne mich inzwischen recht gut aus, auch mit dem Mod-R/M und dem S-I-B Byte Kram. Und ich glaube, so ein Assembler (den man einfach zwischendurch mal über seinen Code laufen lassen kann (kurz aus IDE ins DOS wechseln, assemblieren, zurückwechseln) wäre ein Segen für viele Borland-Pascal+ASM Coder.

Aber ich komme eben schon kaum zu etwas, murks hier auch so rum. Technisch wär's für mich Null Problemo, so einen Assembler zu bauen. Parsen mach ich inzwischen im Halbschlaf, so oft, wie ich das schon gemacht hab...
Und ASM86FAQ stellt das alles so dar, daß es sogar direkt pars-bar wäre. Außerdem hab ich hier noch 'n echt gutes File, das 'n Kumpel direkt aus der entpackten EXE des coolen Demos "Into the Shadows" von Triton gerippt hat und dieses File ist quasi wie direkt für einen Assembler gebaut.
zatzen hat geschrieben:ASM86FAQ gibt auch einiges her, wenn man mal nicht nur nach Taktzyklen der Befehle Ausschau hält. Nur eben zu 32 Bit Konstantenübergabe in Pascal-Assembler habe ich dort nichts gefunden - weil es so direkt ja nicht möglich ist.
Ja, die ASM86FAQ sollte man unbedingt immer griffbereit haben. Unverzichtbares Werk, IMO.
Benutzt Du da auch, so wie ich, meine FAQREAD.EXE?
zatzen hat geschrieben:Ich bin das sehr langsam und überlegt angegangen, habe aber schonmal die simplere "blocks2vga" Routine bis auf weiteres fertig. Mehr Optimierung bekomme ich erstmal nicht hin, und da steckt schon einige Denkzeit drin. Du schreibst, Du liest nicht gerne fremden Code, aber Du kannst ja mal drübersehen, ist ja ziemlich trivial, und die Carry-Geschichten stammen ja von Dir selbst.
Mag sein, aber ich lese trotzdem nicht gerne fremden Code. Immer so aufwendig, sich da reinzudenken - und jeder hat einen anderen Stil. Ich kriege schon immer die Krätze von 0f531h -Hexzahlen, weil ich seit jeher die $f531 -Nethode gewöhnt bin... Da seh ich dann 'ne Zahl... und erst am Ende: Achso, ein h, also hex...
zatzen hat geschrieben:Vielleicht langweilig für Dich, aber für mich ist es noch relativ spannend, möglichst effektiv programmieren zu lernen.
Naja,... "langweilig"... - eher aufwendig, wenn jemand mir einen Klotz OPTIMIERTEN Code da vor die Füße wirft und ich soll den nachvollziehen... Wo ich ja schon mit meinem eigenen Zeug kaum hinterherkomme.
zatzen hat geschrieben:Eigentlich eine so banale Sache, aber bedeutsam, wenn es gut performt. Eigentlich ist es ja mein Kram und ich sollte Dich nicht damit belästigen, aber mir liegt viel dran dass die Performance so gut ist wie nur möglich.
Naja, wie soll ich das jetzt nett genug ausdrücken? Wenn es so wichtig ist, wie es performt, damit man auf der anderen Seite wieder Performance vertun kann mit extrakomplexen Spritepatternpackroutinen... Wie soll man das finden?
zatzen hat geschrieben:Vielleicht siehst Du auf Anhieb noch Möglichkeiten zur Verbesserung.
Wie der Kaiser sagen würde: Schaumermal...
zatzen hat geschrieben:Noch eine Anmerkung: Ich brauche BX ab 0, daher "getmem(..., 1016); inc(blkinfo_seg); blkinfo_ofs := 0" - ich hoffe das ist so korrekt.
Ist es nicht. Da hast Du wohl bisher Glück gehabt. Du mußt natürlich trotzdem den restlichen Offset mit einbeziehen, auch wenn Du die unteren 16 Bytes davon ignorierst! Richtig ist:

Code: Alles auswählen

inc(blkinfo_seg,succ(blkinfo_ofs shr 4)); blkinfo_ofs:=0;
zatzen hat geschrieben:Pointer habe ich ABSOLUTE auf die ofs/seg Words deklariert.
Hier also der Code, um Blöcke vom Bildpuffer nach VGA zu kopieren. Ist ein riesen Gerät geworden, aber assembliert immer noch kleiner als ein Blockinfo-Datenfeld.

Code: Alles auswählen

Ja, hier war dann so ein abartiger Code-Block, den ich durchlesen sollte...
Also, nur kurz:
Man kann auch FS und GS direkt (mit Speicher) laden, da muß man auch nicht über Register gehen. Bei ES hast Du's ja auch gemacht... Naja, wenn erstmal irgendwann mein Assembler kommt, bin ich bestimmt der totale HELD der DOS-Coder-Fraktion...
zatzen hat geschrieben:Die Blockprüfung geschieht 2-Byte-weise. Dadurch können zwei Blöcke, die an geraden Positionen nebeneinander liegen mit einer schnelleren "Doppelroutine" auf einmal kopiert werden. Das wird in der Praxis häufiger vorkommen, bei 2 Blöcken nur bei geradem Info-Offset, ab 3 Blöcke garantiert. Bis zu inkl. JCXZ @... sind es nur 2 Takte, in diesem Fall dann umgerechnet für einen Block nur 1 Takt reine Blockprüfung, plus eben die 77 Takte für die Doppelroutine und den Sprung hin und zurück.
Ja, die Idee klingt zwar nett - aber ich wage blasphemischerweise zu glauben, daß man damit (gegenüber der "Einzelprüfung") nicht viel spart, weil der "kompliziertere" Test den Speedgewinn wieder auffrißt. Muß man sehen...

Außerdem: Ich versuchte irgendwie nachzuvollziehen, was Du mit diesem @copy0: @copy1... usw zu bezwecken versuchtest, aber, IMO:
ich vermute stark, Du willst damit eine Multiplikation vermeiden.
Aber sowohl vom Speicherverbrauch als auch vom Speed her kommst da mit ner Lookup-Table besser. Und wo Du sowieso schon BP hast (also ein Index-fähiges Register), kannst auch gleich sowas wie

Code: Alles auswählen

add BP,BP;mov BP,CS:[@Table+BP];
oder so machen.
Und, wie gesagt, habs eher überflogen. Aber an der Stelle LEA, wo steht {obere 16 bit egal}... Egal sind die nicht. Im 16bit-Mode müssen bei so Adressierungen die oberen 16bit der Zieladresse =0 sein, sonst rastet die Maschine aus. Weiß nicht, ob LEA da auch maleurt, aber Speicherzugriffe auf sowas tun es dann definitiv.
zatzen hat geschrieben:Ich zähle für den ersten Sprung "nach oben" in die weitere Umleitung nur zwei Takte, da ein Jcc bzw. JCXZ bei "branch not taken" einen Takt braucht und sonst zwei mehr, und dieser eine Takt gehört immer zur Blockprüfung und nicht zur Ausführung des Kopierens.
Ja, Du optimierst immer ausgehend vom 486er, oder?
zatzen hat geschrieben:Für den Fall, dass nicht beide Blöcke "TRUE" (= 0) sind, habe ich im Code sozusagen noch zwei Fliegen mit einer Klappe geschlagen, mit TEST CX, 0FFFEh gleichzeitig CL und CH unabhängig voneinander auf 0 geprüft (sind entweder je FF oder 0). Mittels Parity (mal ne sinnvolle Nutzung - man sagt diesem Flag ja keine besondere Nützlichkeit nach) und Sign.
1.) WER sagt dem Parity-Flag keine besondere Nützlichkeit nach?
2.) Hab ich so noch nie probiert - obwohl ich das Parity auch schonmal etwas "kreativ um die Ecke" eingesetzt hab.
Aber ich sag's mal so: Gerade mit den FLAGS mach ich 'n Haufen interessantes/cooles Zeug - denn die sind ja ein riesiger Vorteil von ASM, den KEINE mir bekannte Hochsprache bietet.
zatzen hat geschrieben:Ich hätte stilistisch mit dem Zero Flag auch noch eine dritte Fliege dabei, für den Fall beide sind Null, hätte dann nur TEST GS:[BX+?], REG machen müssen (2 Takte), lediglich gefolgt von drei Jcc's, was aber die Performance unterm Strich verschlechtert und trotzdem ein (dazu auch noch beständiges) Register gebraucht hätte. Achja, und endlich habe ich mal eine sinnvolle Bekanntschaft mit LEA gemacht.
Ja, LEA hilft z.B. auch manchmal innerhalb von Procedures/Functions. Wieso? Weil der Assembler natürlich vorher nicht einen Offset (einer Referenz eines Objekts, das erst im Unterprogramm deklariert wird, kennen kann - das geht nur für's Hauptprogramm.
zatzen hat geschrieben:Pro zwei geprüfte Blöcke werden für den Fall "nichts kopieren" 5 Takte verbraucht, also auf einen Block umgerechnet 2,5 Takte. Für die Schleife und diverses Resetten/Updaten außen herum fallen 20 Takte pro 40 Blöcke an, das sind 0,5 pro Block, also kann man sagen im "Worst Case" 3 Takte pro Blockprüfung. Es ist also nicht vermessen zu sagen, dass ich für das Testen von 1000 Blöcken nicht mehr als 3000 Takte brauche. Mit 3000 Takten schafft REP MOVSD 4000 Byte, diese Menge verliere ich an Durchsatz.
Ehrlich, ich glaub' das jetzt einfach mal. Ich zähl' jetzt nicht DEINE Taktzyklen in DEINEM Code. Sorry, falls das etwas oberflächlich 'rüberkommt - aber da hab ich echt jetzt keinen Bock drauf, während ich das hier schreibe.
zatzen hat geschrieben:[noch mehr Taktzyklenzeug...]
REP MOVSD schafft damit nur 53332 Bytes... Wiederum, Theorie... Also wenn jemand so faul ist, mit REP MOVSD zu kopieren anstatt das etwas weitläufiger zu unrollen, dann ist meine Blockroutine - theoretisch - in jedem Fall schneller...
Naja, es gibt außer Performance noch andere Eckdaten, die ein Programm haben kann. Selbstredend könnte man auch 16000x mov EAX,ES:[xxx];mov DS:[xxx],EAX; machen und so - die totale Oberklasse des "kopiere 320x200 Screen" - aber, wie ich immer so sage: Trade-Off (Speicher/Performance)...
zatzen hat geschrieben: - Okay, ich habe es getestet. Ein bisschen ernüchternd, aber noch brauchbar: Bis etwa 500 Blöcke zieht meine Routine mit REP MOVSD gleich. Dosbox ist allerdings kein Anhaltspunkt, und in diesem Fall zu meinem Vorteil. Dosbox emuliert REP MOVS? offenbar zu schnell, und meine Theorien sind durchaus haltbar.
Ich ahne da etwas, dass Dosbox generalisiert quasi einen Pentium emuliert. Und bei dem braucht REP MOVSD nur 1n.
Ich habe mal getestet: Unrolled (EAX beladen, Speicher mit EAX befüllen (stupide nur "auf einer Stelle", dürfte genügen als Simulation, oder?), je 80 mal und in Schleife 200x) vs. REP MOVSD für 64000 Bytes. Ergebnis: Unrolled braucht doppelt so lange. Gleiches passiert wenn ich einfach nur 16000x Register schaufle, ohne Speicherzugriff. Daher kann man Prefetch-Penalties auch ausschliessen, d.h. sollte Dosbox die emulieren. DosBox behandelt REP MOVSD offenbar mit 1n, egal welcher Prozessortyp und Core eingestellt ist. Auf richtiger Hardware, wo REP MOVSD dann tatsächlich 3n "hat", könnten meine Theorien dann ziemlich gut hinkommen. Allemal besser als sich von einem 3-fach so schnellen REP... täuschen zu lassen, so dass das Ergebnis hinterher nur auf Pentiums läuft.
OK, jetzt hast Du es wohl ENDLICH selbst mal gemerkt, wie wenig DOSbox als Benchmark geeignet ist.

Ich erkläre dann mal kurz, was DOSbox ist und wie es funktioniert:
Die Entwickler von DOSbox sagen selbst, daß es dazu dient, alte Spiele zu spielen und NICHT, eine exakte Abbildung irgendeines real existierenden Systems zu sein.

Um also etwas zu emulieren, muß die CPU, die Grafikkarte, die Soundkarte, diverse Chips (Kbd-Controller, oder z.B. der coole DMA-Chip) in ihrer FUNKTION möglichst genau emuliert werden. Außerdem braucht der Emulator "für sich selber" ja auch noch so einiges an Power.

Dazu muß das Hostsystem, auf dem der Emulator läuft, natürlich VIEL schneller sein als das zu emulierende System. Deshalb versucht der Emulator, erstmal alles so schnell wie möglich zu emulieren - denn jeden Befehl einzeln entsprechend "auszubremsen", nur damit die gewünschte Endgeschwindigkeit stimmt, wäre viei zu aufwendig. Also wird das zum Schluß gemacht: Am Ende wird, wenn und falls es noch zu schnell ist, durch warten an den Framegrenzen das Ganze Ding runtergebremst. (Dient nur dazu, diese alten, ungetimeten Spiele spielen zu können - für neuere, getimete Spiele - also spätestens 386er Zeug - kann man DOSbox auf Anschlag stellen und es läuft trotzdem wie es muß. Spätestens zu 386er-Zeiten hat kein Schwein mehr mit Opcode-Zyklen auf PC "getimed".)

So, wieso schreib' ich das? Um folgendes zu erklären: DOSbox arbeitet wie eine VM. Eine VM besteht zu einem nicht unerheblichen Teil aus einem PARSER, der die ganzen Opcodes (also Befehle/Parameter) nacheinander parst und dann ausführt.

Und wenn er VIELE Befehle parsen muß, geht das mehr zu Lasten der Performance, als wenn er WENIGE Befehle parsen muß. Will sagen:
Wenn er 1000 "unrolled Loop" Befehle einzeln parsen und dann ausführen muß, ist er langsamer, als wenn er nur einmalig die zwei: REP MOVSx... parsen muß (und dann weiß, was zu tun ist und es einfach in einer internen Schleife ausführen läßt.

Will sagen: Dadurch ist es - unrealistischerweise - so, daß unrolled Loops auf 'nem Emulator langsamer ausgeführt werden als dieser REP-Kram - obwohl es in der Realität genau andersrum wäre.

Ein Emulator hat schon genügend damit zu tun, "gleichzeitig" die CPU, die Grafikkarte (VGA mit seinen komischen Besonderheiten!), den Sound (SB mit ihren komischen Besonderheiten) und anderes so zu emulieren, daß es funktioniert und prüft sicher NICHT live nach, ob ein bestimmter Codebereich vielleicht Teil einer "unrolled Loop" sein könnte, um den irgendwie "priorisiert" in einem Spezial-Unterprogramm auszuführen.

Also, anders gesagt: Ja, optimieren ist 'ne super Idee - bin ich total dafür. Aber in einem Emulator/VM sieht man vom Ergebnis/Erfolg des Ganzen quasi NIX. Für einen Emulator ist Code umso einfacher auszuführen, je "primitiver" und "dümmer" er gemacht ist.

Jeder coole "Code-Trick", der auf einer realen Maschine die entscheidenden Ticks mehr bringt, ist ein Ding, das der Emulator erkennen und umsetzen muß. Wie schonmal erwähnt: Beim C64 und anderen 8-Bit-Kisten ist beim Emulieren das am aufwendigsten/schwersten umsetzbare die ganzen "Hardware-Bugs", die für diverse Coder-Tricks benutzt werden. Kannst Dir sicher vorstellen, wenn der AGSP-"Hack" auf 'nem realen C64 schon nicht ganz ohne zu coden ist, wie asozial sich das erst EMULIERT, daß das dann auch so funktioniert!
zatzen hat geschrieben:Es gibt nach meinem Geschmack jeweils einen Sprung zu viel. Und JMP CX ist auch etwas langsam. Aber irgendwoher müssen die Kopierroutinen ja die Koordinaten und die Rücksprungadresse beziehen können, und ich wollte das BP-Setting nicht in die Blockprüfung einbauen.
Ich kann den Einfluss des Prefetchings übrigens noch nicht so recht einschätzen und kenne mich damit zu wenig aus. Deswegen hauptsächlich nur die Einschätzung durch Takte-zählen.
OK, also, soweit ich weiß für Prefetch-Queue:
86: 6 Bytes
286: 8 Bytes
386: 16 Bytes
486: 32 Bytes
Alles danach wird entsprechend erkannt - also einen Pentium kann man nicht mehr mit Selbstmod-Code >32Bytes vom orig. Standpunkt aus schrecken. Der nimmt das mit in den Cache.

Und dabei gleich zum Thema Cache: Du sagtest letztens (oder hier?), daß bei ASM86FAQ die exakten Takte drinstehen und man deshalb ja auch, wenn einem die Register ausgehen, ohne Gewissensbiß auch Speicher belästigen kann. Ich darf dann an dieser Stelle vielleicht mal ebenfalls die ASM86FAQ.TXT zitieren:

Bei allen Taktzyklenangaben handelt es sich um bereinigte Werte, d.h. es wurden folgende Bedingungen zugrunde gelegt:


* Buszugriffe ben”tigen keine WAIT STATES.
* Durch die Ausfhrung des Befehles wird keine Exception ausgel”st.
* Es liegen keine lokalen HOLD Requests an.
* Cache Hit bei jedem Sprungziel-, Daten- oder Befehlszugriff.
þ Speicheroperanden sind aligned (auf optimale 0MODx Boundaries).
* keine AGIs bei EA-Berechnungen.
* TLB Hit bei jeder Page Translation.

Also, der 5. Punkt spielt vor allem bei 286er und darunter noch eine Rolle - deshalb gibt's ja in Pascal diese Einstellung, alles über Bytes auf gerade Adressen zu alignen. Aber vor allem der 4. Punkt: "CACHE HIT" ist das, was ich meine! Eine CPU hat interne 1st-Level und externe oder interne 2nd-Level-Caches, d.h. kleine Speicherbereiche, die die CPU schneller/"direkt" ansprechen kann. (Bei heutigen "modernen" CPUs sind die 2nd-Level Caches so groß, daß ein DOS-Programm komplett darin ausgeführt werden könnte, ohne einen einzigen Zugriff auf realen RAM.)

Der Cache "wandert" mit beim Ausführen von Code bzw. Zugriff auf Daten. Mit Cache-Hit ist gemeint, daß der Zugriff auf einen Bereich innerhalb des Cache "trifft" und dann entsprechend mit den bei ASM86FAQ angegebenen Zyklenzahlen ausgeführt wird.

Genau das (gepaart damit, daß der RAM der VGA eigener RAM ist, der nicht im "normalen" RAM-Bereich liegt) ist der Grund für langsame Zugriffe auf so Zeug. Denn Zugriff auf Grafikkarten-RAM kann die CPU nicht "cachen" - das "sieht" ja die Grafikkarte nich, was da im Cache der CPU rumliegt. Ähnliches Problem ergibt sich natürlich für DMA: Das muß natürlich dann im "richtigen" RAM liegen, sonst findets die Soundkarte nicht. D.h. bei so DMA-Aktionen muß natürlich eventuell noch im Cache rumliegendes Zeug vorher in den Speicher geflusht werden.

Also: Zugriff auf RAM ist *kein* gleich-schneller Ersatz für Arbeit mit Registern. Punkt.
zatzen hat geschrieben:Ich musste die Adressierung "mov fs:[di+bp+?]" inline schreiben, der Pascal Compiler packt bei Benutzung vom Override $64 und BP ein $3E (also DS Override) mit rein, wodurch der $64 Override dann nicht mehr gilt. Mag er anscheinend nicht...
Mag er schon. Darauf fällt man nämlich gerne mal rein. Man schreibt ja manchmal DS: oder auch garnix, weil man ja weiß, daß DS: das Standardsegmentregister ist, das "sowieso immer" benutzt wird und dann kann man eben seine schönen Segment-Overrides ($64 / $65) davorklatschen....

ABER (das bitte irgendwo dick und rot markieren) :
FÜR ALLE ADRESSIERUNGEN, DIE BP ENTHALTEN, IST SS DAS STANDARD-SEGMENTREGISTER!
Also: Bei Adressierungen mit [BP], [BP+SI], [BP+DI], [BP+x], [BP+SI+x], [BP+DI+x] ist das Standardsegmentregister SS. Wenn man nix davorschreibt, macht Pascal da den DS: Override, weil es denkt, man wird schon DS wollen. Wenn man aber selber SS: davorschreibt, läßt es das weg und man kann selbst ungestört seinen Segment-Override machen!
zatzen hat geschrieben:Also soviel dazu. Mir liegt viel daran, hier möglichst viel Performance zu haben, sorry für so viel Brimborium, wir haben sowieso schon viel drüber diskutiert. Aber es scheint sich zu lohnen.
Ja, wenn's das Ergebnis rechtfertigt, lohnt sich viel der Mühe. Manchmal staunt man echt, was diese alten Kisten für Dinge anstellen können (und wundert sich dann, wieso Windows auf NEUEN Kisten, die ca. mindestens um Faktor 300 schneller sind, trotzdem so abstinkt... - Scheinen wohl nicht alle so einen Bock auf Performance zu haben, wie's aussieht...)
zatzen hat geschrieben:Aber es hat sich gelohnt, habe wieder etwas gelernt: Befehle wie MOV, LEA und Sprünge lassen die Flags in Ruhe. War mir bisher nicht so klar dass man sowas wie TEST AX, 0FFFEH machen kann, über JP irgendwo hinspringen, dort rumschaufeln was das Zeug hält, und dann als nächstes ganz einfach nochmal mit JNS woanders hinspringen kann. Sehr Takte-sparend.
Das hätt ich Dir auch sagen können. Und außerdem steht's ja in diesen "Kästen" zu den Befehlen bei ASM86FAQ, welche Flags wie beeinflußt werden. Und ich habe mehr als einmal schon verflucht, daß SHL/SHL NICHT das Zero-Flag beeinflussen, sondern leider nur das Carry... (An anderen Stellen war's dann natürlich wieder 'n Vorteil. Man kann eben nicht alles haben.)
zatzen hat geschrieben:Eine Frage zu LEA: Ich hätte zwischendrin mal LEA EBP, [EBX*8+0] gebraucht. Scheinbar geht das nicht aus dem 16 Bit Modus heraus.
Habs noch nicht getestet - aber:
1.) Im 16-Bit-Mode ist LEA natürlich nur zur Berechnung von 16-Bit-Offsets zuständig!
(Wenn man das o.g. will, muß man natürlich $67 und $66 präfixen.)
Und, wie schon erwähnt: UNBEDINGT dafür sorgen, daß das, was bei EBX*8+0 rauskommt, <=$FFFF ist. Also EBX muß nicht nur vorher <=$FFFF sein, sondern auch das Ergebnis muß im 64kB-Bereich liegen.
zatzen hat geschrieben:Abschliessender Test: 16000x EBX nach EAX und EAX nach EBX kopieren (80x unrolled) vs. Blockroutine: Blockroutine ist bei < 1000 Blocks immer schneller und bei 1000 Blocks etwa gleich auf. Also wenn man es so betrachtet - optimal.
Wie gesagt: Eigentlich wäre das unrolled deutlich schneller als der REP-Kram und Schleifen-Kram. Habe ja schon oben erklärt, wieso das bei DOSbox etwas komische Ergebnisse bringt.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ich mache es in meinen Games genauso: Eine Hauptschleife, in der nacheinander "immer im Kreis" die ganzen Dinge ausgeführt werden, wenn das "Flag" (bzw der Zähler) drüber ist. Aber in diesem Fall ist die Framerate NICHT KONSTANT[...]
Ich könnte mir das bei mir eher nur so vorstellen, dass ich eine Basis-Framerate habe von der zeitlichen Länge eines Soundpuffers, und dass dann evtl. die Framerate für die Grafik entsprechend halbiert etc. wird, bei Performanceeinbruch. Aber lass mich erstmal ein Spiel auf meine Weise versuchen. Das Block-Dingen ist nicht zuletzt aus dem Problem heraus entstanden, dass ich die Framerate erstmal fix gestalten möchte.
Ach, wie gesagt - da rede ich keinem rein. War nur die Antwort auf Deine Bemerkung mit der Schleife.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ein Mißverständnis. Ich warte in TESTGAME nicht auf Retrace, soweit ich mich erinnere.
Ich meinte die Blöcke-Demo, da ist die Framerate mit auf Retrace warten "gerastert" und bricht dann sofort erstmal auf die Hälfte ein wenn's nicht mehr reicht. Sowas in etwa könnte ich mir noch vorstellen selber zu realisieren in einem Spiel.
Ehrlich? Sowas findest Du gut? Ich finde das maximal Scheiße. Bei einem Spiel, wo die Performance vielleicht etwas unter max. des Systems ist, hätt ich lieber die Wahl statt der maximalen 50 FPS vielleicht nur 45 oder 40 FPS zu haben, anstatt, wenn es die 50 knapp nicht schafft, gleich auf bloß 25, 16.66, 12.5, 10, 8.333 usw runterzugehen...
zatzen hat geschrieben:
DOSferatu hat geschrieben:Man muß es ja leider 2-stufig machen: Erst die komplette (Wieder-)herstellung ALLER Bereiche, die von Sprites belegt sind... [...]Weil man damit überdeckte (bereits in diesem Frame gepixelte) Sprites wieder teilweise oder vollständig löschen würde.
Wenn man Blockwiederherstellung und Sprite-Zeichnen für jedes Sprite direkt nacheinander machen würde würde das aber auch je nachdem zu mehr Blocktransfers führen, weil bei Überlappung ja gleiche Blöcke markiert werden. Finde ich also schon in Ordnung so.
Genau das meinte ich ja! Sowohl die sinnlose Doppelmarkierung - als auch das "Abschneiden" bereits restaurierter Bereiche.
zatzen hat geschrieben:Woran ich vorab schonmal denke: Ich möchte Hintergrundblöcke mit Transparenzmöglichkeit haben, damit ich z.B. eine Hintergrundebene habe und eine Vordergrundebene. Dazu muss es entsprechende Blockroutinen geben. Vielleicht ist hier eine unrolled Variante am besten, wenigstens 8x, und vielleicht kann man ja noch eine andere Lösung finden als sich Byteweise durch die Register zu prüfen.
Ohne Dich nerven zu wollen: Es gibt KAUM eine andere Lösung. Die einzig andere Lösung ist, die Blocks als eine Art RLE zu speichern. D.h. man gibt an, wieviele "deckende Pixel" kommen und dann wieder, wieviele ausgelassen werden. DAS ist die einzig andere Lösung. Das funktioniert zwar, erfordert aber eine vorige Aufbereiung der Daten und bei der Darstellung der Blocks ist man weniger flexibel, weil man nicht mehr "mitten aus dem Block" lesen kann. Und "unrollen" wird dabei schwerer.

Wenn man sowohl sowas will als auch Unroll-Performance, wäre es wohl am besten, so eine Art "Darstellungs-Skript" zu erfinden, wo die Sachen quasi von einem externen Tool in so ein Format "vorberechnet" werden und von einer simplen aber superschnellen Routine nur noch hingeballert werden müssen.
zatzen hat geschrieben:Bei der Blockroutine konnte ich ja mit test reg, 0FFFEh direkt low und high gleichzeitig testen, aber da wären dann auch wieder jumps. Vielleicht ist es auch möglich ohne Sprünge Zielregister weiterzudrehen bei transparenter Farbe. Muss ich mal drüber nachdenken. Hmm, "cmp al, 1; adc bp, 0" und dann eben mit "al, mov ds:[si+bp]" und "mov es:[di+bp], al".
Du wirst lachen: Den Trick mit dem Carry als "Übersprung-Addierer" benutz ich schon seit Jahren in etlichen Varianten... - Ich spar' mir deshalb mal, den Code zu zitieren.
zatzen hat geschrieben:Gewöhnlich ist also gleich lang wenn nicht gesprungen wird, sonst einen Takt länger und es wird eben gesprungen. Und "gewöhnlich schneller" ist entweder (teils deutlich) schneller oder gleich, beide gewöhnlichen Varianten sind ohne BP Nutzung entsprechend unrolled schneller. Und einen Haken hat die Version ohne Sprünge: Wenn das letzte Element transparent ist wird danach noch ein unerwünschtes Byte an höchster Adresse + 1 kopiert.
Ja, an der Stelle greift dann wieder mein Trick mit der "umgeklappten Schleife", die am Anfang auch die Endbedingung abfragt und wo am Anfang mitten in die Schleife reingesprungen wird.
zatzen hat geschrieben:Kann man vielleicht durch unrolling und dann anderes Ende lösen, aber irgendwie gefällt mir das ganze auch nicht so richtig, auch wegen dem 2x einlesen. Zudem ist das Hochzählen von BP unberechenbar und somit kann man das ganze nicht unrolled strukturieren.
Naja, Unrollen ist auch nicht die Antwort auf alles - auch, wenn es schon sehr beeindruckende Ergebnisse bringen kann. Wenn man alles "um das Unrolling herum" konstruiert, kann es einem leicht passieren, daß man mit der "Vorbereitung" der ganzen Dinge den Performancegewinn wieder auffrißt. Immer den Brutto-Gewinn im Auge behalten. "Eine beliebte Idee auf alles draufschmeißen" löst (leider) auch nicht jedes Problem...
zatzen hat geschrieben:Naja, meine ersten Gehversuche mit nicht ganz so trivialem Code... Aber es macht Spaß über kleinen Sachen länger zu brüten, wenn man dann eine kompakte Lösung findet.Der Gedanke hier war, ich möchte in den Blockdaten die 64 Bytes nicht überschreiten, sonst haut es am Ende mit der Adressierung nicht hin.
Ja, wie ich schon sagte... Aber das hängt ja vom "Bild" ab - und egal, wie man's programmiert: Für jeden Code gibt es an Daten einen "Worst Case"...
zatzen hat geschrieben:Deswegen kann ich die Daten schlecht RLE-mäßig anlegen, d.h. Info-Byte wieviel transparente Blöcke kommen, dann Info-Byte wieviel nicht-transparente usw. im Wechsel, das könnte alles ziemlich aufblasen je nachdem, auf maximal ca. 96 Bytes. Wobei das schon geht, wenn das Bild ansonsten einige Blöcke beinhaltet, die einfarbig oder komplett transparent sind.
Ja, da muß man unter Umständen was am Bild machen - weil man sich nicht drauf verlassen kann, daß jeder Code mit jedem Bild ein optimales Ergebnis bringen wird, wenn man nicht weiß, wie das Bild "aussieht".
zatzen hat geschrieben:
DOSferatu hat geschrieben:[Blockfummelroutine vs. REP MOVSD]Nicht nur deshalb - sondern weil natürlich eine durchgehende Routine schneller ist als eine, die jedesmal einzeln zur Blockposition muß, also diese vorberechnen und mit Einzelbefehlen einen Kasten füllen.
Das ist mir bisher noch etwas unklar, rein Taktmäßig sagtest Du (und ASM86FAQ) REP MOVSD braucht 3n Takte. Einzelbefehle (2x MOV 32 Bit) brauchen aber vergleichsweise nur 2n Takte. Die Berechnung der Position erfolgt bei meiner Routine in einem einzigen LEA-Befehl, abgesehen davon dass gesprungen werden muss und BP ein Wert zugeordnet werden muss, und ein Takt für das einlesen des Rücksprungs draufgeht - mehr aber nicht.
Ja, natürlich braucht Unrollen noch weniger. Aber das war damals ja (noch) nicht das Thema.
zatzen hat geschrieben:
zatzen hat geschrieben:Und klar, dass 1000x BlockGefummel, selbst in bestem Assembler, langsamer ist als 64000 Byte per REP MOVSD.
Um mich selbst zu zitieren... Ich bin da jetzt verwirrt. Es heisst 3n, aber in Dosbox sieht das mehr nach 1n aus. Vielleicht hast Du Klarheit auf einer realen Maschine.
Mit der Aussage wollte ich ausdrücken, daß einfaches Komplett-Kopieren natürlich schneller ist, als wenn man vor jedem Stück Kopieren erst noch Prüfungen ausführen zu müssen, wenn dann WIRKLICH auch trotzdem ALLE kopiert werden müssen. Der Vorteil des "Bildblock-Markieren-und-nur-diese-Kopieren" ergibt sich nur dann, wenn es IMO bei max. ca. 30% zu kopierender Blocks bleibt. Wenn man mehr als 50% des Bilds mit Blocks kopieren muß, aber die ganze Prüferei, Markiererei mit den Sprites usw. dafür machen muß, kann es durchaus sein, daß man am Ende nichts gewinnt.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ja, meiner Einschätzung nach lohnt es sich wirklich maximal bei einer einstelligen Anzahl (1-9) "kleiner" (max. 32x32) Sprites. Bei allem, was darüber ist, wird man meiner unmaßgeblichen Meinung nach kaum noch Performance rausholen, sondern eher mit komplizierten Routinen herumhadern, die am Ende gar nicht wirklich helfen.
Der Test hat gezeigt dass die Blockroutinen etwa gleich schnell (bei 1000 Blöcken) und schneller (bei weniger) waren, als das ganze entsprechend mit unrolled 32 Bit MOVs. Hatte ich ja schon erwähnt. Daher, wenn es langsam ist, liegt es an den MOVs, weniger an der Blockprüfung und der Koordinaten"berechnung".
Wie gesagt: DOSbox ist überhaupt keine Referenz für eine reale Maschine.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Mit "Mode-X" bezeichne ich zusammenfassend alle Modi, die durch das Unchaining (und die damit verbundene komische *4-Adressierung) die vollen 256kByte VGA-RAM benutzen können. Daß damit auch 320x240 geht, ist nur der Nebeneffekt, weil man dann keinen "Wraparound" mehr hat, wenn man den einstellt. Anders ausgedrückt: Den "Mode-X" gibt es auch für 320x200 - da hat man dann 4 "Bildschirmseiten" - bei 320x240 hat man 3, usw.: Anzahl_Seiten=262144 div (Breite*Höhe).
Ja, ich dachte nur, wenn ich mir schon die Mühe mache, dann will ich auch quadratische Pixel, was gerade bei Emulation sinnvoll ist, weil nicht jeder "dumme User" da einstellt dass der Aspect richtig dargestellt werden soll.
Immer diese quadratischen Pixel. Was ist daran so wichtig? Wenn man die Grafik auf 320x200 entwickelt und auf 320x200 anzeigt, sieht es nicht verzerrt aus. Wenn an die Grafik einscannt und runterskaliert, kann man das einfach im für 320x200 nutzbaren Verhältnis skalieren und es sich ebenfalls nicht komisch aus. Quadratische Pixel sind sowas von unwichtig.

Und, übrigens: Beim realen System braucht keiner "den Aspect richtig einstellen", da füllt das Bild immer den Screen. Wenn man auf einem Emulator den Aspect ausschaltet (um vielleicht schnelleren Bildaufbau zu haben, weil der Hostrechner 'ne Krücke ist), ist man selber schuld. Was ein Spieler mit einem Spiel auf einem Emulator macht, kann man als Entwickler sowieso nicht beeinflussen. Der kann einfach ne falsche Farbpalette benutzen oder das Bild aufn Kopf stellen - dagegen gibts keinen Hack.
zatzen hat geschrieben:Ja und mit Unabhängigkeit der ganzen Prozesse... Ich begreife es ganz langsam wie gesagt. Aber ich versuchs erstmal so nach meiner Idee, irgendwie muss man ja auch aus eigenem Antrieb vorankommen.
Ich habe das nur geschrieben, um zu verdeutlichen, was die Alternative zu einer "im Kreis laufenden Schleife" wäre, wenn man WIRKLICH immer "direkt" auf alle "Meldungen" der Prozesse reagieren wollen würde. Ich habe nicht geschrieben, daß *ICH* das so mache oder daß das jemand so machen soll. Ich hatte ja ebenfalls geschrieben, daß man dafür auch viel Speicher braucht (jeder "Task" seinen eigenen Stack und so). Und so "unkompliziert" wie ich das beschrieben habe, wäre es in Wahrheit ja auch nicht. Dafür ist an einem PC einfach zu viel Geraffel dran, das beachtet werden muß. Wäre Multitasking so einfach, würd's ja jeder machen...
zatzen hat geschrieben:
DOSferatu hat geschrieben:Anm.: Falls Dir "Blättern" gefallen sollte: Mein C64-Spiel "Rockus" scrollt z.B. auch nicht - da wird auch geblättert: 16 Bildschirmseiten pro Level, 27 Levels (9 Hauptlevel à 3 Unterlevel) - und es ist ein Jump'n'run - und ja, sogar mit Musik.
Ich hab mir das letztens nochmal angesehen (ja, auf Youtube). Das ist ja gigantisch. Richtig schöne Grafik und schön klingende Musik, bei der irgendwie lustig ist, dass es alles bekannte Stücke sind, die Du irgendwie etwas anders umgesetzt hast als die Originale, aber man erkennt sie trotzdem.
Also, wenn Du *DAS* schon schöne Grafik findest, hast Du, was Grafik angeht, wirklich außerordentlich niedrige Ansprüche. Und daß die Musik nicht *total* beschissen (sondern nur relativ beschissen) klingt, liegt daran, daß ich dazu ein Musik-Programm (so Editor) von einem C64'er Leser (Stefan Konrath) benutzt habe.
zatzen hat geschrieben:Allein dass so viele Feinde wie vom Himmel rieseln ist etwas ungewohnt für mich.
Ich hatte damals keine Idee für das Spawnen der Feinde - wo allem, weil sie ja mangels Scrolling, nicht so "in's Bild reinkommen" konnten. Deshalb diese blöde Idee mit dem "Feinde runterrieseln". Es sind max. 4 Feinde. Im 1. Level 1, im 2. 2., im 3. 3 und ab dem 4. Level immer 4. (Mehr Sprites hatte ich nicht übrig. Habe kein Multiplexing gemacht.)
zatzen hat geschrieben:Aber an dem Ding kann man gut Dein Potential sehen (wenn Du soetwas auf dem C64 gemacht hast, was kann man dann auf dem PC erwarten...).
Naja, das Ding ist jetzt 27 Jahre alt. Damals war ich jung, hatte noch Zeit, Kraft und Bock. Bock hab ich heute auch noch ab und zu, aber ich bin schon froh, wenn ich mal Zeit habe... und dann gleichzeitig noch KRAFT - also mal nicht müde und nicht genervt sein... Da braucht man eben sehr lange, wenn man so'n Spiel machen will - wegen der vielen "Pausen", wo es wegen des "restlichen Lebens" nicht geht.
zatzen hat geschrieben:Wobei ich für die block2vga Routine mit Absicht DS nicht verwendet habe, damit ich sehe dass es auch ohne dieses funktioniert, und ich es dann in der restoreblocks-Routine verwenden kann. Ich mache mir manchmal eher Sorgen um die Adressregister, so viele sinds ja auch nicht, es sei denn 32 Bit-mäßig kommen noch EAX, ECX und EDX dazu. BP nutz ich gern und oft, SP lässt man aber wohl lieber in Ruhe...
Ja, erstens ist Dir ja bekannt, daß die Adreßregister genau wie die anderen, eigentlich 32bit sind. Mit rol register,16 hat man die gleich mal doppelt... (wenn man mal NICHT den ADD-$FFFF000x-Trick benutzt). Sogar SP ist ja 32bit. Eigentlich kann man also sogar 2 verschiedene Stacks haben. Aber ich will Dich damit mal nicht verwirren...
zatzen hat geschrieben:Lokale Variable vermeide ich zudem lieber (da würde wohl BP benötigt), schon aus Geschwindigkeitsgründen. PUSH ist ja noch flott, aber POP braucht 4 Takte...
Wobei man Register auch in andere Register (oder ihre oberen 16bit) "sichern" kann und selbst sichern in RAM ist teils schneller als PUSH/POP. Das muß man eben von Fall zu Fall abhängig kreativ lösen - da gibt's keine Patentlösung, die IMMER die bestmögliche ist. Das hängt vom restlichen Code ab.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ab 286er erhöht die Anzahl Schiebungen/Rotationen nicht mehr die Zyklen.
Nur um 1 shiften ist langsamer als mit mehr. Aber dafür gibt's ja ADD. Schade dass es kein solches Pendant für SHR 1 gibt.
Gibts doch: DIV 2. Bringt bloß nix... :)
zatzen hat geschrieben:
DOSferatu hat geschrieben:RAM-Zugriffe hängen z.B. von der Zugriffsgeschwindigkeit der RAM-Bausteine ab - bei (langsamem) Grafikkarten-RAM noch deutlicher. Und immer, wenn die CPU einen RAM-Zugriff macht, muß sie auf den RAM warten.
Das ist mir geläufig, und so würde es einem auch gelehrt wenn man sich einen neuen PC zusammenstellen wollte. Bloß, man liest ja in ASM86FAQ eindeutige Wert für RAM-Zugriffe, bei 486ern meist nur 1 Takt. So dass man geneigt sein könnte, die einstige Verkündung ("Assembler ist schnell weil man viel mehr nur mit Registern rechnet") über den Haufen zu werfen und einfach wenn die Register ausgehen ohne schlechtes Gewissen Speicherstellen zu benutzen.
Ach ja, hier ist ja die Stelle. Naja, habe es üben erklärt (Cache-Hit... usw). Also, wie gesagt: Die Zyklenwerte in ASM86FAQ sind "bereinigt", d.h. gelten für den Idealfall. Ein PC ist eine kompliziert zusammengebastelte Kiste. Den Idealfall hat man da nicht immer.
zatzen hat geschrieben:
DOSferatu hat geschrieben:[automatische Spieluhr]Zwar interessant, aber da weiß ich nicht, was ich dazu schreiben soll. Handwerklich bin ich sowas von unbegabt, da kann ich nicht mitreden. Nichtmal, wenn das "Handwerk" aus dem Basteln von Elektronik besteht.
Das sollte nur verdeutlichen, dass ich sozusagen die "Interna" von etwas genießen kann, auch wenn der Endbenutzer diese nie zu Gesicht bekommt und sich nicht dafür interessiert. Aber sollte ja klar sein. So programmiere ich auch nicht nur, weil ich unbedingt dieses erdachte Spiel haben will, sondern das Programmieren selbst und die ganzen krummen Formate sind für mich ebenbürtig.
Geht mir ja ähnlich. Nur ist für mich der klare Unterschied: Einerseits Irgendwelche Testprogramme, an denen ich mich selber ergötze und andererseits reale Dinge wie Spiele oder Tools, die so gut wie möglich funktionieren sollen. Und wenn "so gut wie möglich funktionieren" und "Herumspielerei mit Experimenten" sich nicht vereinbaren läßt, lasse ich die Herumspielerei da drin weg. Auf der einen Seite Schleifenausrollen und Zyklenzählen, nur genug Performance zu gewinnen, damit der dazu da ist, um auf der anderen Seite Spielereien machen zu können, die nur interessant, weil so kompliziert sind (ohne dem Spieler/User am Ende etwas zu bringen), aber das Ganze wieder auffressen... Das wär mir persönlich zu doof. Aber das muß - wie immer - jeder selber wissen.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Man kann nämlich in Xpyderz die Blocks auch z.B. verschiebbar oder als Geheimweg machen...
Das erinnert mich jetzt an Zelda... (NES)
Hab Zelda nie gespielt. Kann dazu also nichts sagen.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Naja, ich plane ja auch viel Zeug der Marke "wird vielleicht mal gebraucht".
Bisher sind es essentielle Dinge im Zusammenhang meiner dürftigen Ideen. ZSM für Sound, ZVID2 für Sprites, Blocktechnik für Performance und Hintergrund"kompression". Mehr ist da eigentlich nicht in Arbeit, aber ich programmiere ja auch weniger als Du.
Das würde ich SO nicht unterschreiben. Ich habe so Phasen, wo ich mal mehr programmiere. Und (leider!) andere - längere - Phasen, wo irgendwie gar nichts wird...
zatzen hat geschrieben:
DOSferatu hat geschrieben:Und dieser 4bit-Sound (pro Stimme), den das Ding generiert, klingt auch nicht grad nach 2020...
Du möchtest/kannst daran wahrscheinlich nichts ändern.
Naja, fand die Idee gut, deshalb *möchte* ich nicht.
Und: Ganzes Ding/Konzept darauf aufgebaut, also *könnte* auch nur dann, wenn ich's komplett neu bauen würde.
zatzen hat geschrieben:Ansonsten ist damit ja nichts verkehrt, der Sound von 8 Bit Systemen ist bloß normalerweise überaus sauber (je nach Sound), wie ich schon sagte, man braucht CD-Eckdaten um den Klang eines NES oder C64 unverfälscht einzufangen.
Ja, spielt aber keine Rolle für mich. Genauso könnte ich sagen: Die Grafik von 8 Bit Systemen hat fast nie quadratische Pixel und das interessiert Dich ja auch nicht.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Nur, wenn sich dann überhaupt einer bereiterklären würde, bei einem Spiel (noch dazu einem DOS-Spiel, an dem nichts zu verdienen ist!) mitzumachen, kann man dem ja keinen hotkeyverseuchten Hex-Editor oder sowas vor die Füße werfen...
Den meisten müsstest Du wohl einen Editor mit Windows-GUI anbieten...
Achnaja - eine GUI hat TheGame3 ja. Mit Fenstern, Eingabefeldern, Pulldowns, Buttons und kombinierter Tastatur- UND Maus-Bedienung (ja, inkl. Mauspfeil). Nur eben in 320x200-Auflösung. Das Ding ist in englisch, hat aber sogar eine integrierte 2-sprachige kontextbezogene Hilfe (die andere Sprache ist nicht Englisch, rate mal, welche ich da spontan genommen hab...) Ich könnt's Dir ja mal schicken, damit Du siehst, wie es aussieht und ob Du damit arbeiten könntest/würdest. Aber ich vermute, Du würdest das sowieso nicht anfassen.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Andererseits braucht ein normaler MOV-Befehl von "irgendwo"/Register nach Register oder von Register nach "irgendwo"/Register jeweils nur 1 Zyklus und somit ist man bei 486er mit komplett manueller Geschichte (also alle einzeln kopieren) schneller als mit REP MOVSD!
Genau das wollte ich hören (bzw. lesen)! Dann wäre die Sache ja klar, der Fehler liegt bei DosBox, und meine Routine performt sehr gut, quasi nach Theorie.
Auch nach Praxis. Siehe oben, meine Erklärung zu DOSbox. Leider wirst Du also nie wirklich testen können, wie verschiedener Code von Dir im Vergleich performt, weil Du immer nur die verfälschten Ergebnisse von DOSbox siehst.
zatzen hat geschrieben:
DOSferatu hat geschrieben:(Anm.: Diesen Text hier schreibe ich übrigens gerade auf meinem selbstgebauten Texteditor, der auch "Highlighting" hat (für die QUOTES und für Kursiv/Fett/Unterstrichen) und der über 500000 Zeichen lange Texte (über 6300 Zeilen) speichern kann.)
Ich mach das innerhalb Windows mit dem simplen Editor und muss alles kursive etc. selbst einstellen.
Genau deshalb hab ich mir den geschrieben. Das "Zitieren" hat mich damals immer Nerven gekostet: Teils das SlashQuote vergessen, teils vertippt und statt eckiger Klammer ] dann die Schweifklammer } gehabt (dann funktioniert's ja nicht). Und dann immer hochladen, Prüfansicht, wieder editieren, wieder Prüfansicht... BOAH hat das genervt. Und wenn einem das mal bei Kursiv oder so passiert ist und dann der ganze restliche Text kursiv war...

Deshalb jetzt also - standesgemäß für'n echten DOS-Fan! - ein Texteditor im 80x25-Zeichen-Textmode! Mit gut lesbaren Zeichen und die ganzen Dinge wie kursive, fette, unterstrichene Bereiche (oder mehr davon) farbig hinterlegt und die Quotes in mehreren Verschachtelungen farbig usw. Und schon merkt man (live! beim Tippen) wenn was nicht stimmt. Die Sachen für Quote, Kursiv, Fett, Unterstrichen und Code hab ich natürlich auch auf Tasten/Tastenkombis gelegt und selbstredend einen Modus drin, der gleich live intern zwischen den Zeichensätzen wandelt UND die Zeichen zählt. Zeichen >=128 zählt er auf Wunsch doppelt, weil ich ja ahne, daß diese 60000er Grenze (man kann die einstellen, dann wird die Anzahl beim Überschreiten rot) für Byte-Anzahl gilt und Zeichen >=128 natürlich wahrscheinlich intern UTF-8-mäßig ("Unicode") gespeichert werden.
zatzen hat geschrieben:Hab ich in diesem Quote mal sein gelassen. Wäre natürlich praktisch, aber ich mache nunmal den Hauptanteil meiner Computerarbeit in Windows. Irgendwann hat es sich so ergeben, ich glaube seit XP. Evtl. könnte ich mir sowas in Freepascal bauen. Dann mit 4000000000 Zeichen langen Texten...
Sobald Du dann auch mal einen 4000000000 Zeichen langen Text damit tippst, gebe ich zu, daß das nützlich ist.
zatzen hat geschrieben:
DOSferatu hat geschrieben:[...]wenn unter Windows gestartet[...]
Geht ja leider seit den 64 Bit nicht mehr...
Künstliche Veralterung durch Industriebonzen ist ja nicht meine Schuld.
286er war 100% abwärtskompatibel zu 8086/186, konnte aber gleichzeitig schon einen PM. 386er war 100% abwärtskompatibel zu 286/186/86, kann den 286er PM auch im 386er darstellen. 486er ebenfalls 100% abwärtskompatibel, trotz neuer Fähigkeiten... Pentium,/II/III/IV.. und alles was danach kam...
Und bei 64-Bit, wo die CPUs schon mehrere Kerne haben, mit 64-Bit laufen und knapp 4 GHz, ist es technisch nicht mehr möglich, abwärtskompatibel zu bleiben? Kannste dem Xmas-Man erzählen! Das ist Absicht! Künstliche Veralterung, damit alte Software nicht mehr läuft und sich jeder alles neu kaufen soll! Weiter nix. Mit technischen Grenzen hat das GARNIX zu tun. So'ne CPU wär stark genug, um, gleichzeitig mit ihrem 64bit-Mode, in irgendner Ecke irgendwas 486er-mäßiges mitlaufen zu lassen, ohne daß das System davon auch nur 3°C wärmer wird...

Ach, was reg' ich mich auf...
zatzen hat geschrieben:
DOSferatu hat geschrieben:Es gibt sowas seit 286er, der Befehl heißt BOUND. Such mal in der ASM86FAQ danach. Er löst INT 5 aus, wenn außerhalb. Ja, INT und so. Aber wenn es selten genug vorkommt und die Prüfung nur "zur Sicherheit" ist, könnte das vielleicht Deinem Anliegen helfen/genügen.
Leider ist es das "Clipping" des Soundpuffers, um ihn von 16 Bit sicher in 8 Bit zu überführen. Kommt sooft vor wie die Samplefrequenz ist.
Ich habe da bislang:

Code: Alles auswählen

{ ax ist signed integer, kann < -128 und  > 127 sein, muss aber auf 0-255 (unsigned) begrenzt und konvertiert werden }
add ax, 128
js @negclip
or ah, ah
jz @noclip_skip
mov ax, 255
jmp @noclip_skip
@negclip:
xor ax, ax
@noclip_skip:

( mov es:[di], al )
Das ist jetzt gefühlt Urzeiten her, und ist wohl noch verbesserungswürdig. Hab es nur mal schnell hervorgekramt.
Anm.: Ja, das ist wieder noch so "hochsprachen-mäßig" gedacht. Davon mußt Du mal wegkommen...

Ich hab da neulich mal rumexperimentiert. Also, wenn man's ganz ohne Sprünge haben will, und NUR AX benutzen will, komm ich so auf 8-9 Befehle, um das abzudecken - damit gewinnt man nix. Wenn man zusätzlich noch CL, ECX oder 16bit der Indexregister (also BX, BP, SI oder DI) opfern will, kommt man auf so ca. 5-6 Befehle. Auch nich so richtig cool - obwohl ich da echt interessante Konstrukte hingezaubert habe. Aber wieso benutzt Du nicht einfach das:

Code: Alles auswählen

add ax,128 {das ist ja noch von Dir}
neg ah
jz @nochange
sar ax,15
@nochange:
Den Vorteil nutzen, daß AX teilbar ist. Und NEG hat den Vorteil, daß es die Flags setzt (im Gegensatz zu NOT) und außerdem, daß es 3 Zustände setzen kann:
0 bleibt 0
+x wird -x
-x wird +x
Das braucht man ja, weil wenn AX>0 ist, auf 255 saturiert werden soll (also quasi auf "-1") und bei AX<0 auf "0". D.h. Du hast einen Sprung und nur insgesamt 2 Befehle. Wenn Du AH natürlich =0 brauchst, mußt Du danach noch xor ah,ah oder sowas machen (VOR dem @nochange:-Label, denn wenn es schon =0 ist, braucht mans ja nicht nochmal auf 0 setzen.)
Und ja: NEG hat zwar den Nachteil, daß $80=$80 bleibt, also Zahlen $8000 bis $80FF (also -32768 bis -32513) würden quasi "falsch saturiert". Aber ich will mal nicht Deine Intelligenz beleidigen, indem ich Dir erkläre, wieso das für Deine vorgesehene Anwendung komplett irrelevant ist.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ja, was tut man nicht alles für Speed. An Deiner Stelle hätte ich mir schon lange mal in Pascal so ein kleines Programm gebaut, was Dir eine ganze Latte aufeinanderfolgender Befehle mit anderen Indizes generiert, die Du dann bloß noch in den Code einfügen brauchst. So einen langwierigen (unlooped) Kram würd ich nie von Hand eingeben.
Mir kam da auch so die Idee, ob man sich selber nicht auch einen kleinen Assembler schreiben könnte, der einen Code durchscannt und nur die für den Pascal Assembler unbekannten Sachen ver"inlinert". So könnte man munter drauf los legen mit 32 Bit Befehlen und müsste nicht ständig externe Assembler bemühen und die Hexfolgen abtippen. Wäre in einer Windows-Umgebung sehr praktisch, dort könnte man den Code Programmübergreifend einfach über Copy+Paste transferieren. Dosbox ist da leider abgeschirmt. Jedenfalls wäre das dann so als hätte Pascal in einem Popup sowas drin, wo man den Code reinschreiben kann und er wird gewandelt. Mal sehen.
Ach, Copy+Paste, Popup... immer dieser verwöhnte Windows-Mist. Kurz aus der IDE raus, 'ne Batch starten, die vielleicht nur Q.BAT heißt (also nur Q Enter) und die gleich ein "exit" am Ende hat - und man hat das Gleiche. Da kann man sich so einen wie von mir obengenannten Assembler in die BAT legen, der ändert die Source und beim Wiedereintritt in die IDE fragt die bloß, ob man den geänderten Source übernehmen will (was man natürlich bestätigt).
zatzen hat geschrieben:Ich habe jetzt hier nicht so viel geschrieben. Ich habe aber alles aufmerksam gelesen, möchte Dich aber auch nicht übermäßig beanspruchen und von Deinen eigenen Sachen abhalten. Mit der Blocksache hast Du mir schon ziemlich viel Zeit gewidmet.
Jaja... Mich hat's damals einfach selbst mal interessiert, ob das Konzept an sich prinzipiell funktioniert. Deine ganze Kritik zur Performance dieses kleinen Erst-Test-Dings habe ich natürlich gelesen. Aber es ist nicht so, als wüßte ich nicht auch selbst, daß/wie man Dinge optimieren kann.
zatzen hat geschrieben:Hey, ich wollte nur schnell vermelden, dass ich auf die Idee gekommen bin dass man bei der Blockprüfung ja auch direkt ein 32 Bit-Register beladen kann, und so wiederum Takte sparen. Wenn Dir spontan irgendwas einfällt zu dem Code im vor-vorherigen Post kannst Du das gerne äußern, ansonsten merke ich, dass mir immer noch was einfällt wenn ich mich in Ruhe damit beschäftige. Ist alles noch nicht spruchreif.
Ach naja, wie gesagt. Manchmal ist es so, daß einfach das ingesamte Prinzip in sich funktionieren muß, bevor man optimiert. Wenn man im Praxistest merkt, daß die Stellen, wo man viel optimiert hat, in Wirklichkeit gar nicht so oft vorkommen, ist es etwas schade um die Mühe. Ich experimentiere hier (hab am letzten Wochenende mal was angefangen) auch wieder ein bißchen herum. Vielleicht schick ich (bzw. leg's auf meinen Webaccount) irgendwann demnächst mal wieder 'n lustiges kleines Programm rüber. Mal sehen...
zatzen hat geschrieben:EDIT: Ich sehe gerade leider, dass das gar nichts bringt. Aus einem 32 Bit Register kann man in dem Zusammenhang hier auch nur zwei Conditions ableiten, bzw. funktioniert "test eax, 0fffe0000h" gar nicht, da Parity nur für das niederwertigste Byte ausgewertet wird.
So läuft es also aus meiner derzeitigen Sicht auf

Code: Alles auswählen

@check0: mov ax, gs:[bx+?]; test ax, 0fffeh; jnp @copy0
@check1: js @copy1
...
hinaus, die TRUE-Markierungen sind dann wieder jeweils $FF, da ich jcxz nicht mehr verwende.
Kleiner Tip: Alles, was wie NEG zwei oder mehr Register setzt, kann man danach z.B. mit LAHF abfangen:
SIGN ist Bit7, Zero ist Bit 6, Bit5 ist immer=0... Nur mal so:

Code: Alles auswählen

lahf
mov BX,AX
shr BX,13
mov/and/or/xor irgendwas,CS:[@TABLE+BX]...
Wie findst'n das?
Und wenn man nach lahf noch ror ah,1 macht, hat man Bit7=Carry, Bit6=Sign, Bit5=Zero, Bit4=0... Und kann danach mit 'nem indizierten Sprung oder so per Entscheidungstabelle quasi 'ne Menge Kram abdecken...
zatzen hat geschrieben:Man kann das großzügig unrollen (z.B. auf 40 check's/copy?'s), das spart eine Schleife) und hat dann am Ende kaum mehr als 2 Takte pro Blockprüfung. Alternativ zu "test ax, 0fffeh" wäre "add ax, ax", wirft in diesem Fall die gleichen Flags und noch Carry, und es sind nur zwei Byte Opcode.
Vielleicht fällt mir noch etwas ein. Ich habe da ersteinmal keine Eile.
Ja, gibt ja 'ne Menge, was man so machen kann. Assembler gibt einem zum Glück ja fast keine Grenzen vor.

Vor 'ner Weile (letztes Jahr, kurz bevor das alte Coding-Board geschlossen wurde), hatten die mal so Wettbewerb, mit möglichst kompliziertem undurchsichtigem Code irgendwas zu machen, z.B. "HELLO WORLD" auszugeben. Mein Beitrag war in 100% Assembler. Falls Interesse, kann ich das ja, inkl. Source, mal hochladen...

OK, erstmal genug für heute.

Re: Eigenes Videoformat

Verfasst: So 5. Jul 2020, 00:12
von zatzen
DOSferatu hat geschrieben:Ich plane schon länger, mal einen Inline-Assembler direkt für Pascal-Sourcen zu coden, der alle Befehle assembliert, aber nur, wenn er erkennt, daß es >=386er Befehle sind, diese dann "inlined" (als Bytes/Words/DWords) hinschreibt und dahinter auskommentiert {...} den vorher eingegebenen ("echten") Befehl.
Exakt die Idee hatte ich auch, aber wenn Du das machst überlasse ich das Dir, ich traue Dir da mehr zu.
DOSferatu hat geschrieben:Ja, die ASM86FAQ sollte man unbedingt immer griffbereit haben. Unverzichtbares Werk, IMO.
Benutzt Du da auch, so wie ich, meine FAQREAD.EXE?
In einer DOS-Umgebung (oder bis Windows XP) bietet sich das unbedingt an. Wenn man mit Windows 7 oder höher arbeitet und um eine Textdatei zu öffnen erst einen Emulator bemühen muss, ist man natürlich geneigt einfach einen Windows-Editor zum lesen zu benutzen, der dann auf einem großen Bildschirm auch noch mehr Zeilen bietet. Gut, im EGA-Modus zieht der FAQREAD gleich auf. Bisher war vor allem die Wortsuche im simplen Windows-Editor blitzschnell, und man kann nach oben wie nach unten suchen. Aber der korrekten Ascii-Darstellung halber und auch um mir die Kapitel mal ordentlich gegliedert anzusehen, werde ich auch mal mit der FAQREAD arbeiten. Bloß wenn man nur mal eben schnell nochmal nachgucken will, wievel Zyklen ein Befehl wie und wann braucht, dann ist das einfach in meinem Fall über den simplen Windows-Editor direkter.
DOSferatu hat geschrieben:Ich kriege schon immer die Krätze von 0f531h -Hexzahlen, weil ich seit jeher die $f531 -Nethode gewöhnt bin... Da seh ich dann 'ne Zahl... und erst am Ende: Achso, ein h, also hex...
Das kann ich nachvollziehen, ich schreibe beim Programmieren z.B. auch alles klein und bin irritiert sobald jemand Großbuchstaben verwendet. Das macht es für mich irgendwie auch unleserlicher. Bei Assembler schreibe ich lieber so, wie es offiziell Standard ist mit den Hex-Werten, auch wenn es innerhalb Pascal ist. 0x???? ist wohl auch Standard (in Pascal wohl nicht), aber das finde ich wiederum hässlich und unleserlich.
DOSferatu hat geschrieben:Naja, wie soll ich das jetzt nett genug ausdrücken? Wenn es so wichtig ist, wie es performt, damit man auf der anderen Seite wieder Performance vertun kann mit extrakomplexen Spritepatternpackroutinen... Wie soll man das finden?
Es soll einfach nicht unnötig Zeit bei den Blockprüfungen verbraten werden. Mit 2 Takten pro Block liege ich da ja schon ganz gut, besser geht wohl nicht. Immerhin spart man ja Restaurieren UND in den VGA Speicher kopieren. Also wiegt das direkt doppelt, vielleicht wiegen dann ein paar aufwendiger entpackte Sprites nicht so schwer. Im Zweifelsfall muss ich die Sprites ungepackt reinklatschen, dann habe ich aber wieder nicht die Motivation die Animationen möglichst üppig auszuschmücken (weil ich den Speicher dafür gar nicht hätte), und DAS merkt dann sogar der Endbenutzer des Spiels.
Wie Taktefressend das Markieren der Spritebereiche wird werde ich erst erfahren wenn ich mich um die Programmierung davon kümmere. Erstmal bange ich um die Daten meiner wichtigsten Festplatte, weil so ein Billigstromkabel abgeschmort ist. Kurzschluss zum Glück im Kabel und nicht in der Platte, das macht Hoffnung.
DOSferatu hat geschrieben:Du mußt natürlich trotzdem den restlichen Offset mit einbeziehen, auch wenn Du die unteren 16 Bytes davon ignorierst! Richtig ist:

Code: Alles auswählen

inc(blkinfo_seg,succ(blkinfo_ofs shr 4)); blkinfo_ofs:=0;
Danke! Aber 16 Byte mehr alloziieren ist korrekt, oder reichen auch weniger? Sorry falls die Frage dumm ist, aber ich meinte Du sagtest mal sowas.
DOSferatu hat geschrieben:Man kann auch FS und GS direkt (mit Speicher) laden, da muß man auch nicht über Register gehen.
Das weiss ich, ich konnte nur dem Online Assembler keine konkreten Speicheradressen der Variablen übergeben...
DOSferatu hat geschrieben:
zatzen hat geschrieben:zatzen hat geschrieben:
Die Blockprüfung geschieht 2-Byte-weise. Dadurch können zwei Blöcke, die an geraden Positionen nebeneinander liegen mit einer schnelleren "Doppelroutine" auf einmal kopiert werden. Das wird in der Praxis häufiger vorkommen, bei 2 Blöcken nur bei geradem Info-Offset, ab 3 Blöcke garantiert. Bis zu inkl. JCXZ @... sind es nur 2 Takte, in diesem Fall dann umgerechnet für einen Block nur 1 Takt reine Blockprüfung, plus eben die 77 Takte für die Doppelroutine und den Sprung hin und zurück.
Ja, die Idee klingt zwar nett - aber ich wage blasphemischerweise zu glauben, daß man damit (gegenüber der "Einzelprüfung") nicht viel spart, weil der "kompliziertere" Test den Speedgewinn wieder auffrißt. Muß man sehen...
Ja, ich fand es erst gut, mit JCXZ mit 2 Takten direkt 2 Blöcke abfangen zu können, aber in der Praxis wird man stets mehr inaktive Blöcke haben als aktive, und daher sollte die Prüfung so ausgelegt sein, dass sie vor allem für den Fall "nichts tun" schnell ist.
DOSferatu hat geschrieben:Außerdem: Ich versuchte irgendwie nachzuvollziehen, was Du mit diesem @copy0: @copy1... usw zu bezwecken versuchtest
Das ist wegen dem Rücksprung, und weil ich in der ungerollten Prüfschleife die Takte möglichst kurz halten will und daher kein Register hochzähle sondern (derzeitige Version) mit "@check2: mov ax, gs:[bx+2]" oder sonstige "+?" arbeite. Daher setze ich dann, wiederum jetzt in der aktuellen Version, jeweils bei einem der z.B. 40 @copy?? labels das Rücksprungziel (mov dx, cs:[offset @check??]) und noch z.B. BP mit 0, 8, 16 usw als X-Koordinate sozusagen. Dann in die Kopierroutine springen, und die springt dann zurück "nach DX". Kostet mich so glaube ich dann 44 Takte für einen kopierten Block.
DOSferatu hat geschrieben:Aber an der Stelle LEA, wo steht {obere 16 bit egal}... Egal sind die nicht. Im 16bit-Mode müssen bei so Adressierungen die oberen 16bit der Zieladresse =0 sein, sonst rastet die Maschine aus.
Wenn ich aber doch adressiere mit mit z.B. "mov eax, fs:[si+bp]", ist es dann nicht egal wie die oberen 16 Bit von ESI und EBP sind? Wenn doch, stünde das etwas konträr dazu dass Du z.B. sagtest, man könnte durch Rotation sogar einen zweiten Stack haben. Oder überhaupt, wenn ich mich jetzt nicht total irre, habe ich in meinem ZSM Player EBP verwendet als 16 Bit Adressierung, und die oberen 16 Bit für den Carry-Trick.
Wenn ich natürlich ein 32 Bit-Konstrukt habe wie "mov eax, fs:[ebx*8 + ebp]", dann ist das was anderes.
DOSferatu hat geschrieben:Ja, Du optimierst immer ausgehend vom 486er, oder?
Ja. Das hat zum einen damit zu tun, dass ein 386er zu langsam für meine Vorhaben wäre, ein Pentium dagegen so schnell dass man praktisch fast auch ohne Assembler auskäme. Und Dosbox spielt mit rein, weil heutige Rechner und wohl sogar Smartphones in der Lage sind, einen 486er zu emulieren. Bei Smartphones bin ich mir aber nicht sicher.
DOSferatu hat geschrieben:Naja, es gibt außer Performance noch andere Eckdaten, die ein Programm haben kann. Selbstredend könnte man auch 16000x mov EAX,ES:[xxx];mov DS:[xxx],EAX; machen und so - die totale Oberklasse des "kopiere 320x200 Screen" - aber, wie ich immer so sage: Trade-Off (Speicher/Performance)...
Das wäre Irrsinn bzw. gar nicht möglich. So ein unrolled Block für einen 8x8-Block, das sind 192 Bytes Kompilat, das mal 1000 und es passt überhaupt gar nicht mehr in ein Segment, und ein Drittel des ganzen Speichers ist futsch, wenn es denn möglich wäre.
DOSferatu hat geschrieben:OK, jetzt hast Du es wohl ENDLICH selbst mal gemerkt, wie wenig DOSbox als Benchmark geeignet ist.
Ja allerdings. Ich habe nur leider keinen 486, könnte nur einen Pentium wieder fit machen, aber das hilft mir dann auch in dem Fall nicht viel. Ich kann nur versuchen, möglichst performant zu programmieren, auch wenn ich es nicht sicher überprüfen kann. Es kann mir aber helfen, Feedback z.B. von Dir zu bekommen, wie geschehen bei ZSMplay oder dem ollen ZVID(1).
DOSferatu hat geschrieben:Also: Zugriff auf RAM ist *kein* gleich-schneller Ersatz für Arbeit mit Registern. Punkt.
Was auf ne Weise ja eher erfreulich ist, dann fühlt sich die Arbeit mit Registern direkt wieder viel sinnvoller an.

- Und ich merke mir das mit SS und BP -
DOSferatu hat geschrieben:[Halbierung der Framerate bei zu wenig Performance]Ehrlich? Sowas findest Du gut? Ich finde das maximal Scheiße. Bei einem Spiel, wo die Performance vielleicht etwas unter max. des Systems ist, hätt ich lieber die Wahl statt der maximalen 50 FPS vielleicht nur 45 oder 40 FPS zu haben, anstatt, wenn es die 50 knapp nicht schafft, gleich auf bloß 25, 16.66, 12.5, 10, 8.333 usw runterzugehen...
Gut finde ich das nicht, aber es wäre derzeit das einzige was ich mir vorstellen könnte zu machen. Und selbst das scheint mir noch schwierig. Also um das Thema erstmal vorläufig zu beenden: Ich sehe mal wie ich mit der ganz-oder-garnicht Methode klarkomme.
DOSferatu hat geschrieben:Ohne Dich nerven zu wollen: Es gibt KAUM eine andere Lösung. Die einzig andere Lösung ist, die Blocks als eine Art RLE zu speichern. D.h. man gibt an, wieviele "deckende Pixel" kommen und dann wieder, wieviele ausgelassen werden. DAS ist die einzig andere Lösung. Das funktioniert zwar, erfordert aber eine vorige Aufbereiung der Daten und bei der Darstellung der Blocks ist man weniger flexibel, weil man nicht mehr "mitten aus dem Block" lesen kann. Und "unrollen" wird dabei schwerer.
Ja, genauso habe ich mir das auch gedacht, und soetwas habe ich vor über 20 Jahren auch schonmal für Sprites gemacht, und dann auch irgendwie noch ein Clipping hingefummelt. Aber damals habe ich noch überhaupt nicht an soetwas wie Unrolling gedacht und war eher begeistert davon, dass Assembler-Schleifen schneller sind als Hochsprachenschleifen.
DOSferatu hat geschrieben:Wenn man sowohl sowas will als auch Unroll-Performance, wäre es wohl am besten, so eine Art "Darstellungs-Skript" zu erfinden, wo die Sachen quasi von einem externen Tool in so ein Format "vorberechnet" werden und von einer simplen aber superschnellen Routine nur noch hingeballert werden müssen.
Das klingt interessant. Aber Du meinst nicht etwa dass Assembler-Code erzeugt wird?
DOSferatu hat geschrieben:Immer diese quadratischen Pixel. Was ist daran so wichtig?
Unter anderem wären die auch praktisch wenn man mal was z.B. auf Youtube zeigen will. Entweder muss man dann die Aspect Ratio über Bord werfen oder man handelt sich bei niedrigeren Auflösungen ziemliche Unschärfen ein. Die heutige "Computerwelt" hat nunmal quadratische Pixel, deshalb wäre es einfach praktisch.
So wichtig ist es mir aber gar nicht, sonst würde ich mich aufs emsigste mit Mode-X beschäftigen. WENN ich dann mal ein Video von nem Spiel auf Youtube hochladen will darf ich dann eben nicht vergessen, die 320x200 auf z.B. 1440x1080 zu resamplen. Das klingt nach Verschwendung, aber auf Youtube kursieren längst Videos mit 4K und höher. Und es gibt da auch so Leute die 320x200 Spiele mit KI auf HD "upscalen". Ganz interessant.
DOSferatu hat geschrieben:Also, wenn Du *DAS* schon schöne Grafik findest, hast Du, was Grafik angeht, wirklich außerordentlich niedrige Ansprüche.
Kommt immer drauf an. Ich hab viel am C64 gesehen was dagegen lieblos wirkte.
DOSferatu hat geschrieben:Hab Zelda nie gespielt.
Ein Blätterer. Ich war fasziniert weil es das erste Spiel war das mir untergekommen war, wo man in alle vier Himmelsrichtungen die Welt erkunden konnte. Da war ich aber falsch motiviert, denn es ist ein Action-Spiel und es sowas kostet mich nerven. Hätte ich mal lieber direkt Point & Click Adventures entdeckt, aber sowas gab es lange auf dem NES nicht. Das erste dort war wohl Maniac Mansion.
DOSferatu hat geschrieben:[TheGame3-GUI]Ich könnt's Dir ja mal schicken, damit Du siehst, wie es aussieht und ob Du damit arbeiten könntest/würdest. Aber ich vermute, Du würdest das sowieso nicht anfassen.
Ich kann es mir gerne ansehen. Nur würde ich nicht anfangen wollen damit etwas zu basteln. Das wäre anders wenn ich nur möglichst bald ein Spiel machen wollen würde egal wie, und ohne meine eigenen Techniken. Du kennst vielleicht auch AGS (zum Point & Click Adventures machen). Das Ding hat ne dicke Anleitung, da muss man sich richtig reinknien. Ich hatte mich schonmal drangesetzt weil ich wirklich mal dachte, ich mache ein Adventure, hauptsache das Spiel wird fertig. Aber dann hab ich mir gedacht, bevor ich jetzt Wochen damit zubringe, dieses Programm zu lernen, da programmiere ich lieber selber von Grund auf.
DOSferatu hat geschrieben:Deshalb jetzt also - standesgemäß für'n echten DOS-Fan! - ein Texteditor im 80x25-Zeichen-Textmode!
Könnte natürlich auch für mich praktisch sein, trotz Emulator. Kommt nur drauf an, wie man den Text reinbringt, auf den man antworten will. Ich kopiere direkt aus dem Forum in den Editor, muss dann aber alle Tags per Hand schreiben.
DOSferatu hat geschrieben:Sobald Du dann auch mal einen 4000000000 Zeichen langen Text damit tippst, gebe ich zu, daß das nützlich ist.
Darauf komme ich gerne in etwa 5000 Jahren zurück.

Vielen Dank für Deinen 0-255 begrenz- bzw. saturier- Code. Da wäre ich wohl so nicht drauf gekommen.
DOSferatu hat geschrieben:Kleiner Tip: Alles, was wie NEG zwei oder mehr Register setzt, kann man danach z.B. mit LAHF abfangen:
SIGN ist Bit7, Zero ist Bit 6, Bit5 ist immer=0... Nur mal so:

Code: Alles auswählen

lahf
mov BX,AX
shr BX,13
mov/and/or/xor irgendwas,CS:[@TABLE+BX]...
Wie findst'n das?
Und wenn man nach lahf noch ror ah,1 macht, hat man Bit7=Carry, Bit6=Sign, Bit5=Zero, Bit4=0... Und kann danach mit 'nem indizierten Sprung oder so per Entscheidungstabelle quasi 'ne Menge Kram abdecken...
Das werde ich in Zukunft berücksichtigen, da ergeben sich ja wirklich neue Möglichkeiten, mit Flags direkt in eine Tabelle reinzugehen. Nur konkret für die Blockprüfung geht es wohl nicht schneller als ein Word einzulesen und dann auf sign/carry und parity zu prüfen.
Aber man lernt immer noch dazu von Dir. Gerade eben wurde auch mir klar, dass "jmp CS:[@TABLE+BX]" wohl auch gültig ist.
Würdest Du sagen dass es besser ist einen Tabellensprung so zu formulieren anstatt erst ein Register mit "CS:[@TABLE+BX]" zu beladen und dann "jmp REG"? Man könnte ja meinen man spart einen Zyklus und braucht kein extra-Register bzw. kann BX hier auf seinem Wert belassen.
DOSferatu hat geschrieben:Vor 'ner Weile (letztes Jahr, kurz bevor das alte Coding-Board geschlossen wurde), hatten die mal so Wettbewerb, mit möglichst kompliziertem undurchsichtigem Code irgendwas zu machen, z.B. "HELLO WORLD" auszugeben. Mein Beitrag war in 100% Assembler. Falls Interesse, kann ich das ja, inkl. Source, mal hochladen...
Ja, warum nicht. Vielleicht blicke ich ja doch ein klein wenig durch und kann noch was lernen. Was man so im Netz an Assembler findet ist meistens so trivial und ineffektiv wie meine ersten Assembler Programme Ende der 90er.