{"id":2720,"date":"2021-01-02T16:58:01","date_gmt":"2021-01-02T16:58:01","guid":{"rendered":"http:\/\/blog.rastersoft.com\/?p=2720"},"modified":"2023-12-25T19:48:34","modified_gmt":"2023-12-25T19:48:34","slug":"pintando-en-el-spectrum-3","status":"publish","type":"post","link":"https:\/\/blog.rastersoft.com\/?p=2720","title":{"rendered":"Pintando en el Spectrum (3)"},"content":{"rendered":"\n<p>El c\u00f3digo anterior es funcional, pero tiene el problema de que ocupa 541 bytes (c\u00f3digo m\u00e1s la tabla de direcciones). Teniendo en cuenta que la memoria de v\u00eddeo son casi siete kbytes, y otros siete m\u00e1s para el segundo buffer, tenemos que esos 541 bytes pueden ser un gasto excesivo de los 34,5 kbytes restantes de un Spectrum 48K (en un 128K tenemos doble p\u00e1gina por hardware, con lo que no necesitamos nada de esto).<\/p>\n\n\n\n<p>Para solucionarlo, en lugar de utilizar una tabla con todas las direcciones de memoria podemos utilizar una tabla s\u00f3lo con las direcciones de inicio de cada bloque de caracteres (o sea, una de cada ocho filas de p\u00edxels). Esto es posible porque pasar de una fila a la siguiente en un bloque de caracteres es relativamente sencillo: s\u00f3lo hay que incrementar el byte alto. As\u00ed, si tenemos en DE la direcci\u00f3n de un byte en una fila, s\u00f3lo necesitamos hacer INC D para pasar a la siguiente fila (siempre que no sea la \u00faltima fila de un car\u00e1cter, claro).<\/p>\n\n\n\n<p>As\u00ed que combinando todo lo anterior, podemos usar esta funci\u00f3n:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted mycode\">    LD SP, tabla_direcciones\n    LD HL, buffer\n    LD BC, 0\n    EXX\n    LD HL, buffer + 6144\n    LD DE, 0x5800 ; zona de atributos de color\n    LD BC, 768 ; 32 columnas * 24 lineas\nloop_l1:\n    EXX\n    INC B ; como BC es cero, esto es igual que LD BC, 256\n          ; esto son 32 x 8 bytes en una fila de caracteres\n    POP DE ; obtenemos la direcci\u00f3n inicial de la fila de caracteres\n    LD IXh, E ; usamos una instrucci\u00f3n no oficial porque\n              ; estamos justos de registros\n    LD A, D\nloop_l2:\n    INC A ; preparamos ya la direcci\u00f3n de la siguiente fila\n          ; hay que hacerlo antes de LDI porque INC modifica\n          ; los flags\n    LDI\n    ... ; 32 LDIs en total\n    LDI\n    LD E, IXh ; recuperamos la posici\u00f3n inicial\n    LD D, A ; y la siguiente fila\n    JP PE, loop_l2\n    EXX\n    LDI\n    ... ; 32 LDIs en total\n    LDI\n    JP PE, loop_l1\n\n...\ntabla_direcciones:\n    DEFW 0x4000, 0x4020, 0x4040, 0x4060, 0x4080, 0x40A0, 0x40C0, 0x40E0\n    DEFW 0x4800, 0x4820, 0x4840, 0x4860, 0x4880, 0x48A0, 0x48C0, 0x48E0\n    DEFW 0x5000, 0x5020, 0x5040, 0x5060, 0x5080, 0x50A0, 0x50C0, 0x50E0<\/pre>\n\n\n\n<p>Y con esto lo tenemos ya, y altamente optimizada, pues en realidad esto ha sido el final de muchas iteraciones. Empec\u00e9 utilizando <strong>DJNZ<\/strong> para las ocho iteraciones del bucle interno (<em>loop_l2<\/em>), y para conservar la direcci\u00f3n inicial de la fila de caracteres, lo que hac\u00eda era meter <strong>DE<\/strong> de nuevo en la pila con un <strong>PUSH<\/strong>, para sacarlo despu\u00e9s de los <strong>LDI<\/strong>s y poder incrementar <strong>D<\/strong> para pasar a la siguiente fila. Esto era mucho m\u00e1s r\u00e1pido que almacenarlo en alguna zona de la memoria (20 Tstados) o que decrementar dos veces el puntero de pila para que volviese a apuntar al valor inicial (12 Tstados).<\/p>\n\n\n\n<p>La siguiente optimizaci\u00f3n que hice fue almacenar el valor de <strong>E<\/strong> en el registro <strong>A<\/strong>, de manera que despu\u00e9s de los <strong>LDI<\/strong>s s\u00f3lo ten\u00eda que hacer un <strong>LD E, A<\/strong> y ya tendr\u00eda el valor original. Por desgracia esto CASI funcionaba, pues en las filas de caracteres <em>7<\/em>, <em>15<\/em> y <em>23<\/em>, al llegar al final de la l\u00ednea se produc\u00eda un desbordamiento y se sumaba uno a <strong>D<\/strong>, con lo que fallaba. Una soluci\u00f3n ser\u00eda reducir el n\u00famero de columnas de 32 a 31, pero no era una soluci\u00f3n muy elegante, as\u00ed que al final decid\u00ed que era mejor almacenar <strong>D<\/strong> en <strong>A<\/strong>, y guardar <strong>E<\/strong> en una posici\u00f3n de memoria. Pero como no existe <strong>LD (nn), E<\/strong> ni la inversa, no pod\u00eda hacerlo directamente&#8230; a menos que usase c\u00f3digo automodificable. \u00bfQu\u00e9 es esto? La instrucci\u00f3n <strong>LD E, 0<\/strong> se codifica como <em>0x1E 0x00<\/em>, siendo el segundo byte el valor a meter en el registro <strong>E<\/strong>. As\u00ed que lo que hac\u00eda era poner un <strong>LD E, 0<\/strong> justo despu\u00e9s de todos los <strong>LDI<\/strong>s, y despu\u00e9s del primer <strong>POP DE<\/strong> almacenaba el valor de <strong>E<\/strong> justo en el segundo byte de la instrucci\u00f3n <strong>LD E, 0<\/strong>. De esta manera, cada vez que se hace una pasada en el bucle, el c\u00f3digo ha cambiado. Aunque es m\u00e1s r\u00e1pido, pues mientras que el par PUSH-POP son 22 Tstados en cada fila, con esto eran s\u00f3lo 11 Tstados dentro de <em>loop_l2<\/em>, no me gusta nada usar c\u00f3digo automodificable, as\u00ed que me devan\u00e9 los sesos hasta que me acord\u00e9 de que ten\u00eda cuatro registros extra de 8 bits: las mitades de <em>IX<\/em> e <em>IY<\/em>. Es cierto que son funcionalidades no documentadas, pero funcionan en todos los Z80. As\u00ed que la soluci\u00f3n fue almacenar <strong>E<\/strong> en <strong>IXh<\/strong> (que consume 8 Tstados), y guardar <strong>D<\/strong> en <strong>A<\/strong>. Y adem\u00e1s, el resultado es ligeramente m\u00e1s r\u00e1pido tambi\u00e9n: aunque dentro del bucle pierdo ocho Tstados, pues <strong>LD E, 0<\/strong> es 1 Tstado menos que <strong>LD E, IXh<\/strong> y el bucle se repite ocho veces, los compenso fuera, pues <strong>LD A, E<\/strong> son 4 Tstados y <strong>LD (nn), A<\/strong> son 13, 17 Tstados en total, mientras que <strong>LD IXh, E<\/strong> son s\u00f3lo 8, con lo que, al final, usando <em>IXh<\/em> en lugar de c\u00f3digo automodificable ahorro 1 Tstado por cada fila de caracteres (8 filas de p\u00edxels). Adem\u00e1s, no hay que olvidar tampoco que esos 7 Tstados se convertir\u00edan en m\u00e1s siempre que hubiese contienda en la memoria, con lo que es un <em>win-win<\/em>.<\/p>\n\n\n\n<p>Y con esto tenemos una funci\u00f3n que ocupa 195 bytes en total (171 bytes m\u00e1s 24 de la tabla), a costa de ser un poco m\u00e1s lenta. \u00bfCuanto m\u00e1s? El bucle interno son 4 + 512 + 8 + 4 + 10 = 538 Tstados, y hay que repetirlo 8 veces. Pero en caso de contienda, ser\u00e1n 544 Tstados, luego la duraci\u00f3n ser\u00e1 entre 4 304 y 4 352 Tstados. El bucle externo son 4 + 4 + 11 + 8 + 4 + 4 + 512 + 10 = 557 Tstados, pero con contienda ser\u00e1n 560 Tstados, y esto repetido 24 veces, una por cada fila de caracteres. Con esto tenemos que copiar una pantalla completa ser\u00e1n entre 116 664 y 117 888 Tstados. Aplicando la f\u00f3rmula de la entrada anterior podemos aproximar a 117 597 Tstados, lo que es superior a los 112 800 Tstados de una pantalla completa. Si s\u00f3lo hacemos 23 filas necesitamos 112 696 Tstados para copiar la pantalla frente a 111 008 Tstados disponibles antes de que nos alcance el haz. Pero si hacemos 22 filas, tardaremos 107 797 Tstados, frente a 109 216 Tstados que tarda el haz en alcanzar ese punto, por lo que con esta rutina, aunque ahorramos casi dos tercios de memoria, perdemos una fila respecto a la rutina anterior.<\/p>\n\n\n\n<p>Sin embargo, no debemos olvidar que esto s\u00f3lo significa que no podemos tener animaciones fluidas en las dos \u00faltimas filas de la pantalla, pero s\u00ed podemos tener gr\u00e1ficos est\u00e1ticos o semi-est\u00e1ticos como un marcador, un inventario&#8230; cosas que no cambien demasiado a menudo de manera que un <em>artifact<\/em> durante su modificaci\u00f3n pase desapercibido.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>El c\u00f3digo anterior es funcional, pero tiene el problema de que ocupa 541 bytes (c\u00f3digo m\u00e1s la tabla de direcciones). Teniendo en cuenta que la memoria de v\u00eddeo son casi siete kbytes, y otros siete m\u00e1s para el segundo buffer, tenemos que esos 541 bytes pueden ser un gasto excesivo de los 34,5 kbytes restantes &hellip; <a href=\"https:\/\/blog.rastersoft.com\/?p=2720\" class=\"more-link\">Seguir leyendo <span class=\"screen-reader-text\">Pintando en el Spectrum (3)<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,17,7],"tags":[20],"class_list":["post-2720","post","type-post","status-publish","format-standard","hentry","category-programacion","category-retrocomputacion","category-tutoriales","tag-pintando-en-el-spectrum"],"_links":{"self":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/2720","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2720"}],"version-history":[{"count":7,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/2720\/revisions"}],"predecessor-version":[{"id":2731,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/2720\/revisions\/2731"}],"wp:attachment":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2720"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2720"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2720"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}