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.