Spider-OS

login - registration

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.

Comments :

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 :
Cube Front

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) :
Axes X, Y, Z

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 :

    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
    	.nvShader	NV_Shader_State
    	.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
    	add r1,r4,oVCLBin.vertex.length
    	strNotAlign32 r0,r1
    	
    	imm32 r0,dataIndicesList			; Address Of Indices List
    	add r1,r4,oVCLBin.vertex.address
    	strNotAlign32 r0,r1	
    	
    	mov r0,3 * 12					; Maximum Index
    	add r1,r4,oVCLBin.vertex.maxindex
    	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
    	fragmentShaderCode2		Fragment_Shader_Code2f
    	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
    	nvShaderState		NV_Shader_State_Record
    	
    	mov r0,sizeof.VertexDataEntrySTR			; Shaded Vertex Data Stride.
    	strb r0,[r4,oNVShaderState.stride]
    		
    	mov r0,3								; Fragment Shader Number Of Varyings
    	strb r0,[r4,oNVShaderState.nbrVarying]
    		
    	imm32 r0,fragmentShaderCode2
    	str r0,[r4,oNVShaderState.addrCode]		
    
    	imm32 r0,textureUniforms 				; Fragment Shader Uniforms Address
    	str r0,[r4,oNVShaderState.addrUniform]		
    
    	imm32 r0,vertexData2 + BUS_ADDRESSES_l2CACHE_DISABLED		; Shaded Vertex Data Address
    	str r0,[r4,oNVShaderState.addrData]	
    	

    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
    	vadd.f32 y7,s4,s5	
    
    	; 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]
    	vadd.f32 z0,s0
    	vdiv.f32 z0,s1					; z0 = (z0 + 1) / 4
    	vstr z0,[r4]					; save z0
    	add r4,20
    	; 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!

    Add a comment

    Page : 1