问题描述
所以我试图使用 Bresenham 算法绘制一个圆,但我似乎无法在位图显示上获得任何输出。也许我的绘图工作不正常,因为它似乎没有将颜色保存到目标地址。
这是我关注的帮助指导我完成此过程的文章。它是用 C++ 编写的 Bresenham's Circle
我很难找到解释如何修改显示的可靠来源,因此我根据这篇文章撰写了我的显示内存。 drawing a 8x8 unit in BitMap Display Mips
我有解释过程的注释,并使用 $a 寄存器作为指定的参数寄存器
.text
main:
li $a0,50 # x
li $a1,50 # y
li $a2,30 # r2
jal circleBres
li $v0,10
syscall
# $a0: passed as xc
# $a1: passed as yc
# $a2: passed as the radius - r
circleBres:
li $s0,0x00FF0000 #loads the color red into the register $a2
add $sp,$sp,4
sw $ra,0($sp)
move $s1,$a0 # this is xc
move $s2,$a1 # this is yc
move $s3,$a2 # this is r - this is y
li $s4,0
li $a0,3
li $a1,2
sub $s5,$a0,$a1 # d = 3 - 2
mul $s5,$s5,$s3 # d = d * r
move $a0,$s1 # Set parameter registers
move $a1,$s2 # to proper values and call
move $a2,$s4 # the first draw
move $a3,$s3
jal drawCircle
circleBresLoop:
bge $s3,$s4,end # Break if y >= x
add $s4,1 # x++
blt $s5,else # if (d > 0) otherwise else
sub $s3,$s3,1 # y--
# d = d + 4 * (x - y) + 10
sub $t0,$s3 # $t0 = (x - y)
mul $t0,$t0,4 # $t0 = $t0 * 4
add $t0,$s5 # $t0 = $t0 + d
add $t0,10 # $t0 = $t0 + 10
j skip
else:
# d = d + 4 * x + 6
mul $t0,4 # $t0 = x * 4
add $t0,6
skip:
move $a0,$s4 # draw
move $a3,$s3
jal drawCircle
j circleBresLoop
end:
lw $ra,0($sp)
add $sp,4
jr $ra
# $a0: passed as xc
# $a1: passed as yc
# $a2: passed as x
# $a3: passed as y
drawCircle:
add $sp,-4
sw $ra,0($sp)
#1
add $s1,$a2 # xc + x
add $s2,$a1,$a3 # yc + y
move $a0,$s1
move $a1,$s2
jal drawPixel
#2
sub $s1,$a2 # xc - x
add $s2,$s2
jal drawPixel
#3
add $s1,$a2 # xc + x
sub $s2,$a3 # yc - y
move $a0,$s2
jal drawPixel
#4
sub $s1,$a2 # xc - x
sub $s2,$s2
jal drawPixel
#5
add $s1,$a3 # xc + y
add $s2,$a2 # xc + x
move $a0,$s2
jal drawPixel
#6
sub $s1,$a3 # xc - y
add $s2,$a2 # yc + x
move $a0,$s2
jal drawPixel
#7
add $s1,$a3 # xc + y
sub $s2,$a2 # yc - x
move $a0,$s2
jal drawPixel
#8
sub $s1,$a3 # xc - y
sub $s2,$s2
jal drawPixel
lw $ra,4
jr $ra
# $a0: passed as x
# $a1: passed as y
drawPixel:
move $t3,0x10000100 #t3 = first Pixel of the screen
sll $t0,9 # y = y * 512
addu $t0,$a1 # $t0 = x + y
sll $t0,2 # $t0 = xy * 4
addu $t0,$t3,$t0 # adds xy to first pixel ($t3)
sw $s0,0($t0) # Put RED ($s0) in $t0 memory spot
jr $ra
我也试过这只是一个概念证明,看看我可以获得视觉效果,但我不完全理解定位或原因
.text
li $a2,0x00FF0000 #loads the color red into the register $a2
li $s3,50 #y1 = y position of the tail
li $s0,50 #x2 = x position of the head
DrawPixel:
li $t3,0x10000100 #t3 = first Pixel of the screen
sll $t0,9 #y = y * 512
addu $t0,$s0 # (xy) t0 = x + y
sll $t0,2 # (xy) t0 = xy * 4
addu $t0,$t0 # adds xy to the first pixel ( t3 )
sw $a2,($t0) # put the color red ($a2) in $t0
解决方法
第一个添加到 $sp
的符号错误。
您是否注意到在单步执行中,从 drawCircle
调用 circleBres
后,您在 s 寄存器中的局部变量被破坏了?
您要注意保留 $ra
寄存器以备后用,但不保留 s 寄存器。
s
寄存器并不神奇——如果您要使用它们,则必须保留它们的原始值。在这里,drawCircle
重新利用了一些 s
寄存器,而它们同时被 circleBres
使用,因此这是一个冲突,最后修改 wins。调用约定说那些需要被保留,这通常是通过在序言中保存它们的值并在退出时恢复这些原始值来完成的。按照调用约定,drawCircle
应该做同样的事情,但 main
不会将任何值放入 s 寄存器中,所以这里无关紧要。
无论您是否意识到,您都面临着如何在多个功能之间共享有限数量的 CPU 寄存器的困境。有两种方法可以解决这个问题:
在不同的函数中使用完全不同的寄存器——这可能是在有限的情况下作为一种优化来完成的,但是你可以想象如果你有超过 4-6 个左右的函数,你会用完寄存器。
-
使用调用约定:大多数此类约定将寄存器分为两组:调用破坏和调用保留。前一组中的寄存器可以不受惩罚地使用,但需要注意的是,它们不会在具有与调用前相同值的函数调用中幸存下来。可以使用后一组中的寄存器,但前提是它们在返回给调用者之前恢复到其原始值。这两个集合服务于两种常见的使用模式(临时/临时,以及跨函数调用所需的变量)。
call clobbered set 对于需要在内部做一些工作而不涉及函数调用的函数很有用。这些寄存器可以随意使用,而且本质上不需要保存/恢复开销。
调用保留集对于需要围绕函数调用做一些工作的函数非常有用——例如,如果您有一些想要在寄存器中的变量,并且这些变量在函数调用之前建立(设置)并在函数调用之后使用。这些寄存器将在函数调用中幸存下来——但前提是所有函数都遵守规则,也就是说,如果它们也想使用这些寄存器,它们必须在返回给调用者之前恢复原始值。
我已经说明了这一点:调用约定说需要保留调用保留的寄存器(此处为 s),通常通过在序言中保存它们的值并恢复这些原始值来完成退出时结语,如果被函数使用。
所以,s寄存器可以随意使用,只要遵循保存规则即可。这种保存方式与您处理 $ra
寄存器的方式非常相似,因此请对此进行扩展。 (您还可以找到一些示例程序,它们将进一步演示如何正确使用 s 寄存器。)
t
寄存器不一定像 a
寄存器那样在函数调用中幸存下来。
再说一遍:没有魔法——s
寄存器在函数调用中幸存下来的唯一原因是函数遵守使用规则。 t
和 a
寄存器不一定在函数调用后仍然存在的原因也是协议(调用约定)。
考虑到上述内容,让我们看看以下代码片段:
#1
add $s1,$a0,$a2 # xc + x
add $s2,$a1,$a3 # yc + y
move $a0,$s1 <-------- what do you think happens to xc here?? (hint: $a0 holding xc is overwritten,not by drawPixel but by this very instruction
move $a1,$s2
jal drawPixel
#2
sub $s1,$a2 # xc - x <--- what do you think is in $a0 here,after you've already clobbered it above? (hint: nothing useful)
您需要考虑变量中保存的值以及这些变量在汇编/机器代码中的存储位置,而不仅仅是单个指令。没有什么神奇之处:如果你覆盖了一个包含重要值的寄存器,那么它就消失了。处理器会盲目地按照你说的去做;它不知道你的意图。
由于您同时需要 xc
和 x
,因此在调用之后但它们在 a
寄存器中,您需要将它们移动到可以在函数调用中幸存的地方 - 这可能是 s
寄存器或内存。
没有意义
add $s1,$a2 # xc + x
...
move $a0,$s1 <--- should target $a0 directly,above,instead of putting into $s1 then moving here to $a0
...
jal drawPixel
...
sub $s1,$a2 <--- overwriting $s1 proves that we didn't need xc+x to be preserved from above
您在此处使用 s
寄存器作为临时寄存器(这是不合适的),并且还期望 a
寄存器在第一次调用 drawPixel
时仍然存在(正如您所看到的,即使是传递一个参数的行为会破坏 $a0
)。
哦,还有一件事:位映射显示通常位于 0x10010000
(不是 0x10000100
)。