 Spider-OS

Back to the post list

Videocore : textured cube in rotation
2019 Apr 5

That's it, I finally managed to make 3D with the Videocore. I made a program that displays a cube with a texture on each side. In addition, it is rotated.

I had to learn some notions of 3D to display the cube in perspective correctly. You can see below a video of the result obtained with my Raspberry Pi.

It interests you ? The tutorial here.

Aran (webmaster)
2019 Apr 5 21:00

The base of the program is similar to the one used to display triangles in the previous post :Videocore : draw triangles. I will only indicate here the modifications if there is.

1. The triangles of the cube

To start, you have to understand how to draw a cube. From the Videocore point of view, the cube is only a collection of triangles. The cube has 6 faces : front, left, right, up, down, and back. Each face is a square, which is drawn with 2 triangles.

Here is a front view : The front is composed of triangle 1 and 2. The right side is composed of triangle 3 and 4. So on.

Each triangle has 3 vertices, as everyone knows ;-) Here the triangle 1 is composed of vertices V0, V1, and V2. The triangle 2 is composed of vertices V0, V2 and V3. Etc.

This information will be used in the programming. Here is a table that lists all 6 faces of the cubes, with the associated vertices :

table 1
face triangles vertices
front 1 & 2 0, 1, 2, 0, 2, 3
right 3 & 4 0, 3, 4, 0, 4, 5
top 5 & 6 0, 5, 6, 0, 6, 1
left 7 & 8 1, 6, 7, 1, 7, 2
bottom 9 & 10 7, 4, 3, 7, 3, 2
back 11 & 12 4, 7, 6, 4, 6, 5

2. Texture coordinates

You may have noticed in the previous figure: there are coordinates for each vertex. Of the form Vn : X, Y, Z. They indicate to the Videocore how to draw the texture on the triangle. We thus specify in a 3D representation, the position of each vertex. By convention, the axes are positioned like this (Z being the depth) : We consider that the origin is in the center of the cube. If we take the vertex V1 for example, we see that it is to the left of the center of the cube, and at the top on the front face. So if we follow the figure above, we get -X, Y, Z, which corresponds to -1.0, 1.0, 1.0.

Here is the table with the coordinates of all the vertices :

table 2
vertex x y z
0 1.0 1.0 1.0
1 -1.0 1.0 1.0
2 -1.0 -1.0 1.0
3 1.0 -1.0 1.0
4 1.0 -1.0 -1.0
5 1.0 1.0 -1.0
6 -1.0 1.0 -1.0
7 -1.0 -1.0 -1.0

3. 3D coordinates on a 2D screen

It is well known the screen is flat ;-) It is necessary to give the illusion of 3D. This is where the right triangle intervenes with the sines and cosines. I'm not going to do a 3D class, so I'll just give you this link : a complete tutorial on OpenGL.

For summary, here are the mathematical formulas to apply to project 3D positions on a 2D screen. We indicate the angle with respect to the center of the cube. The formulas are therefore applicable to perform rotations on all axes.
In each formula, we take the current position of the vertex, which is updated according to the requested angle.

• Angle on the X axis :
• y = y * cos(angle) - z * sin(angle)
z = y * sin(angle) + z * cos(angle)
• Angle on the Y axis :
• z = z * cos(angle) - x * sin(angle)
x = z * sin(angle) + x * cos(angle)
• Angle on the Z axis :
• x = x * cos(angle) - y * sin(angle)
y = x * sin(angle) + y * cos(angle)

Moreover one must take into account the perspective, and the distance of view towards the cube.
For that, one multiplies the coordinates obtained by a magnifying factor, and one produces a perspective with depth Z. It is also necessary to position the cube on the screen by modifying its origin. The formula is the following :

• factor = fov + (distance * z)
• xn = x * factor + origin_x
• yn = -y * factor + origin_y

xn and yn are then the real coordinates of the pixel, which can be used for Videocore.

4. The setting of the texture unit

It is with the unit Videocore TMU, that we can display a texture on a triangle. The texture can be of several different formats. To simplify the generation of the bitmap, I use the RGBA32R format. All TMU parameters are specified in the Videocore manual section 4.
An essential reference also, which allowed me to understand the functioning of the Videocore at the most ready material: JayStation2 Dev Blog.

There are two texture modes: the normal 2D texture mode, and the cube map mode. In 2D mode, the texture is displayed on a 2D shape with the coordinates x (S), y (T). The cubemap mode allows you to take 3D into account with the z (R) parameter. It is in this mode Cubemap that we will draw the 6 faces of the cube with the texture, using the vertex of table 2.

Here is the code to perform the setting of the TMU :

macro Tex_Config_Param0 base, cswiz, cmmode, flipy, type, miplvls:0
{
dw base OR (cswiz SHL 10) OR (cmmode SHL 9) OR (flipy SHL 8) \
OR ((type AND 0xf) SHL 4) OR miplvls
}

macro Tex_Config_Param1 type4, height, etcflip, width, magfilt, minfilt, wrap_t, wrap_s
{
dw ((type4 AND 0x10) SHL 27) OR (height SHL 20) OR (etcflip SHL 19) OR (width SHL 8) OR (magfilt SHL 7) OR (minfilt SHL 4) OR (wrap_t SHL 2) OR wrap_s
}

macro Tex_Config_Param23 ptype, param2, param3
{
dw (ptype SHL 30) OR (param2 SHL 12) OR param3
}

struc Texture_Uniforms		; align 16
{
Tex_Config_Param0 TEX_T_FMT_ADDR, 0, TEX_MODE_CUBEMAP, 0, TEX_DATA_FORMAT_RGBA32R, 0

Tex_Config_Param1 TEX_DATA_FORMAT_RGBA32R, TEX_T_FMT_HEIGHT, TEX_ETC_FLIP_OFF, TEX_T_FMT_WIDTH, TEX_MAXFILT_NEAREST, TEX_MINFILT_NEAREST, TEX_WRAP_MODE_REPEAT, TEX_WRAP_MODE_REPEAT

; parameters 2 & 3
Tex_Config_Param23  0, 0, 0
}

TEX_T_FMT_ADDR is the address of the texture. TEX_T_FMT_WIDTH and TEX_T_FMT_HEIGHT are the block/pixels number over the width and height.

5. Indexing vertex for binning

In the previous post : Videocore : draw triangles, we set the binning to display a triangle, via the command Vertex Array Primitives. This control provides the three necessary vertices.

Here for the cube, we must display 12 triangles, and use accordingly 12 * 3 = 36 vertices. It takes a lot of memory space. Since vertices are reused several times (see Table 1), we can optimize by indexing the vertices.
For that we will use the command Indexed Primitive List in the list of controls of Binner. In the data, instead of pointing to the coordinates of the vertices, we will specify the address of the index table (table 1).

Here is the corresponding code :

struc V3DControlListBinnerI
{
.tile			Tile_Binning_Mode_Configuration
Start_Tile_Binning
Increment_Semaphore
.clip			Clip_Window
.conf		Configuration_Bits
.viewport		Viewport_Offset
.vertex		Indexed_Primitive_List
Flush
.end:
}
virtual at 0
oVCLBin V3DControlListBinnerI
end virtual

align 16
v3dCLBin		V3DControlListBinnerI

dataIndicesList:
db 0, 1, 2, 0, 2, 3		; front : triangle 1 & 2
db 0, 3, 4, 0, 4, 5		; right : triangle 3 & 4
db 0, 5, 6, 0, 6, 1		; top : triangle 5 & 6
db 1, 6, 7, 1, 7, 2		; left : triangle 7 & 8
db 7, 4, 3, 7, 3, 2		; bottom : triangle 9 & 10
db 4, 7, 6, 4, 6, 5		; back : triangle 11 & 12

mov r4,v3dCLBin
mov r0,Mode_Triangles + Index_Type_8
strb r0,[r4,oVCLBin.vertex.data]

mov r0,3 * 12					; nb indices = 3 vertices * nb triangles
strNotAlign32 r0,r1

imm32 r0,dataIndicesList			; Address Of Indices List
strNotAlign32 r0,r1

mov r0,3 * 12					; Maximum Index
strNotAlign32 r0,r1

6. Vertex coordinates.

We just saw how to index the vertices. The coordinates of these vertices are specified only once with a new structure Vertex_data_Texture_Indexed, as below.

The vertex is composed of the position of the point on the screen x, y, the depth Z, of W, and the coordinates S, T, and R for the texture (table 2).

; for a cube: 8 vertices
struc Vertex_data_Texture_Indexed
{
.v0		Vertex_Data_Entry_STR
.v1		Vertex_Data_Entry_STR
.v2		Vertex_Data_Entry_STR
.v3		Vertex_Data_Entry_STR
.v4		Vertex_Data_Entry_STR
.v5		Vertex_Data_Entry_STR
.v6		Vertex_Data_Entry_STR
.v7		Vertex_Data_Entry_STR
}

; STR is normalized cubemap direction vec
struc Vertex_Data_Entry_STR
{
.coor			Vertex_Data_Entry
.varying0			dw 0.0			; varying S : rx
.varying1			dw 0.0			; varying T : ry
.varying2			dw 0.0			; varying R : rz
}
virtual at 0
oVertexDataSTR Vertex_Data_Entry_STR
sizeof.VertexDataEntrySTR = \$ - oVertexDataSTR
end virtual

struc Vertex_Data_Entry
{
.x			dh 0				; X In 12.4 Fixed Point
.y			dh 0				; Y In 12.4 Fixed Point
.z			dw 1.0			; Z
.w			dw 1.0			; 1 / W
}

align 16
vertexData2				Vertex_data_Texture_Indexed
align 16
align 16
textureUniforms			Texture_Uniforms

My program will therefore initialize this vertex structure, which will be used by the Videocore to display the faces of the cube in 3D.

7. The program

We start by setting the Shader State :

align 16

mov r0,sizeof.VertexDataEntrySTR			; Shaded Vertex Data Stride.

mov r0,3								; Fragment Shader Number Of Varyings

imm32 r0,textureUniforms 				; Fragment Shader Uniforms Address

I'm using a complementary structure dataVertexIndex to save the current vertex coordinates. This allows me to make several passes, and update the coordinates to rotate the cube.

imm32 r4,dataVertexIndex

imm32 r0,ORIGIN_XF
vldr s6,[r0]					; center of the cube on the X axis (floating point)
imm32 r0,ORIGIN_YF
vldr s7,[r0]					; center of the cube on the Y axis (floating point)

sina	equ s0
cosa	equ s1

x0		equ s8				; alias on the vertices
y0		equ s9
z0		equ s10
;...
x7		equ s29
y7		equ s30
z7		equ s31

; loading the vertices of the cube
imm32 r0,dataVertexCube		; table 2
vldm r0!,{x0-z7}

Implementation of what we have seen chapter 3 ;-) We calculate the position of all vertices according to the angles of each axis.

; rotation on the Z axis
mov r0,25					; angle of rotation in degrees
bl sincos						; s0 = sin(angle) ; s1 = cos(angle)

; v0
vmul.f32 s2,x0,cosa
vmul.f32 s3,y0,sina
vmul.f32 s4,x0,sina
vmul.f32 s5,y0,cosa
vsub.f32 x0,s2,s3				; x0 = x0 * cosa - y0 * sina
vadd.f32 y0,s4,s5				; y0 = x0 * sina + y0 * cosa
; vx...
; v7
vmul.f32 s2,x7,cosa
vmul.f32 s3,y7,sina
vmul.f32 s4,x7,sina
vmul.f32 s5,y7,cosa
vsub.f32 x7,s2,s3

; rotation on the X axis
mov r0,15					; angle of rotation in degrees
bl sincos						; s0 = sin(angle) ; s1 = cos(angle)

; v0
vmul.f32 s2,y0,cosa
vmul.f32 s3,z0,sina
vmul.f32 s4,y0,sina
vmul.f32 s5,z0,cosa
vsub.f32 y0,s2,s3				; y0 = y0 * cosa - z0 * sina
vadd.f32 z0,s4,s5				; z0 = y0 * sina + z0 * cosa
; vx...

; rotation on the Y axis
mov r0,r10					; angle of rotation in degrees
bl sincos						; s0 = sin(angle) ; s1 = cos(angle)

; v0
vmul.f32 s2,z0,cosa
vmul.f32 s3,x0,sina
vmul.f32 s4,z0,sina
vmul.f32 s5,x0,cosa
vsub.f32 z0,s2,s3				; z0 = z0 * cosa - x0 * sina
vadd.f32 x0,s4,s5				; x0 = z0 * sina + x0 * cosa
; vx...

We apply the 3D projection.

; v0
vldr s1,[FLOAT_16_0]			; distance = 16
vldr s5,[FLOAT_200]			; fov = 200
vmla.f32 s5,s1,z0				; factor = fov + distance * z0

vmul.f32 x0,x0,s5				; x0 * factor
vmul.f32 y0,y0,s5				; y0 * factor
vadd.f32 x0,s6,x0				; x0 = origin_x + x0
vsub.f32 y0,s7,y0				; y0 = origin_y - y0
vcvt.u16.f32 x0,x0,4			; conversion in 12.4 fixed point
vcvt.u16.f32 y0,y0,4			; conversion in 12.4 fixed point
vmov r1,x0
strh r1,[r4],2					; save x0
vmov r1,y0
strh r1,[r4],2					; save y0
vldr s0,[FLOAT_1_0]
vldr s1,[FLOAT_4_0]
vdiv.f32 z0,s1					; z0 = (z0 + 1) / 4
vstr z0,[r4]					; save z0
; vx...

8. The last step

We launch the Videocore processing :

bl v3dBinnerPrep
bl v3dBinnerRun
bl v3dRenderColor
bl v3dRenderRun

And the result ? This is the next bug :

Z depth is not taken into account. And suddenly the videocore shows in the foreground the last triangle drawn.

We must modify our program shader like this :

struc Fragment_Shader_Code2f		; align 16
{
dw 0x203e3037, 0x100049e0			; fmul  r0, w, varying
dw 0x019e7a00, 0x10020e67			; fadd  tmu0_t, r0, r5

dw 0x203e3037, 0x100049e0			; fmul  r0, w, varying
dw 0x019e7a00, 0x10020ea7			; fadd  tmu0_r, r0, r5

dw 0x203e3037, 0x100049e0			; fmul  r0, w, varying
dw 0x019e7a00, 0x10020e27			; fadd  tmu0_s, r0, r5
dw 0x009e7000, 0xA00009e7		; signal TMU texture read : load data from tmu0 to r4

dw 0x159cffc0, 0x10020b27			; mov tlb_z, rb15 ; nop

; exporting read texture data to MRT0
dw 0x159E7924, 0x10020BA7		; mov tlbc, r4

dw 0x009e7000, 0x500009e7			; nop	; sbdone
dw 0x009e7000, 0x300009e7			; nop	; thrend
dw 0x009e7000, 0x100009e7			; nop
dw 0x009e7000, 0x100009e7			; nop
dw 0x009e7000, 0x100009e7			; nop
}

And here it works :-)

9. The demo

In bonus here is the demo ready to be put on a blank SD card to test on your Raspberry Pi : demo2.zip

w1ll12520114
2019 Apr 9 02:37

You did this from scratch? Oh my.. I actually cant believe it. Good job my friend!!
Aran (webmaster)
2019 Apr 9 22:12

Yes thank you :-) a long-term job but I'm not unhappy with the result. It sounds amazing indeed when you see that everything comes out of simple assembler code ! This is why I did a tutorial : to understand the path. And also it allows me not to forget :-)
w1ll12520114
2019 Apr 11 00:48

Indeed. Keep track of everything! It helps alot when you need reference, documentation and so on.. It helps you learning from your mistakes.

Keep us updated my friend!