1
2/*
3 * Copyright (C) 1999  Brian Paul   All Rights Reserved.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
19 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 */
22
23
24/*
25 * texdown
26 *
27 * Measure texture download speed.
28 * Use keyboard to change texture size, format, datatype, scale/bias,
29 * subimageload, etc.
30 *
31 * Brian Paul  28 January 2000
32 */
33
34
35#include <stdio.h>
36#include <stdlib.h>
37#include <malloc.h>
38#include <math.h>
39#include <GL/glew.h>
40#include "glut_wrap.h"
41
42
43static GLsizei MaxSize = 2048;
44static GLsizei TexWidth = 1024, TexHeight = 1024, TexBorder = 0;
45static GLboolean ScaleAndBias = GL_FALSE;
46static GLboolean SubImage = GL_FALSE;
47static GLdouble DownloadRate = 0.0;  /* texels/sec */
48
49static GLuint Mode = 0;
50
51
52/* Try and avoid L2 cache effects by cycling through a small number of
53 * textures.
54 *
55 * At the initial size of 1024x1024x4 == 4mbyte, say 8 textures will
56 * keep us out of most caches at 32mb total.
57 *
58 * This turns into a fairly interesting question of what exactly you
59 * expect to be in cache in normal usage, and what you think should be
60 * outside.  There's no rules for this, no reason to favour one usage
61 * over another except what the application you care about happens to
62 * resemble most closely.
63 *
64 * - Should the client texture image be in L2 cache?  Has it just been
65 *   generated or read from disk?
66 * - Does the application really use >1 texture, or is it constantly
67 *   updating one image in-place?
68 *
69 * Different answers will favour different texture upload mechanisms.
70 * To upload an image that is purely outside of cache, a DMA-based
71 * upload will probably win, whereas for small, in-cache textures,
72 * copying looks good.
73 */
74#define NR_TEXOBJ 4
75static GLuint TexObj[NR_TEXOBJ];
76
77
78struct FormatRec {
79   GLenum Format;
80   GLenum Type;
81   GLenum IntFormat;
82   GLint TexelSize;
83};
84
85
86static const struct FormatRec FormatTable[] = {
87  /* Format   Type                         IntFormat   TexelSize */
88   { GL_BGRA, GL_UNSIGNED_BYTE,            GL_RGBA,        4    },
89   { GL_RGB,  GL_UNSIGNED_BYTE,            GL_RGB,         3    },
90   { GL_RGBA, GL_UNSIGNED_BYTE,            GL_RGBA,        4    },
91   { GL_RGBA, GL_UNSIGNED_BYTE,            GL_RGB,         4    },
92   { GL_RGB,  GL_UNSIGNED_SHORT_5_6_5,     GL_RGB,         2    },
93   { GL_LUMINANCE, GL_UNSIGNED_BYTE,       GL_LUMINANCE,   1    },
94   { GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, GL_LUMINANCE_ALPHA,   2    },
95   { GL_ALPHA, GL_UNSIGNED_BYTE,           GL_ALPHA,       1    },
96};
97static GLint Format;
98
99#define NUM_FORMATS (sizeof(FormatTable)/sizeof(FormatTable[0]))
100
101static int
102BytesPerTexel(GLint format)
103{
104   return FormatTable[format].TexelSize;
105}
106
107
108static const char *
109FormatStr(GLenum format)
110{
111   switch (format) {
112      case GL_RGB:
113         return "GL_RGB";
114      case GL_RGBA:
115         return "GL_RGBA";
116      case GL_BGRA:
117         return "GL_BGRA";
118      case GL_LUMINANCE:
119         return "GL_LUMINANCE";
120      case GL_LUMINANCE_ALPHA:
121         return "GL_LUMINANCE_ALPHA";
122      case GL_ALPHA:
123         return "GL_ALPHA";
124      default:
125         return "";
126   }
127}
128
129
130static const char *
131TypeStr(GLenum type)
132{
133   switch (type) {
134      case GL_UNSIGNED_BYTE:
135         return "GL_UNSIGNED_BYTE";
136      case GL_UNSIGNED_SHORT:
137         return "GL_UNSIGNED_SHORT";
138      case GL_UNSIGNED_SHORT_5_6_5:
139         return "GL_UNSIGNED_SHORT_5_6_5";
140      case GL_UNSIGNED_SHORT_5_6_5_REV:
141         return "GL_UNSIGNED_SHORT_5_6_5_REV";
142      default:
143         return "";
144   }
145}
146
147/* On x86, there is a performance cliff for memcpy to texture memory
148 * for sources below 64 byte alignment.  We do our best with this in
149 * the driver, but it is better if the images are correctly aligned to
150 * start with:
151 */
152#define ALIGN (1<<12)
153
154static unsigned long align(unsigned long value, unsigned long a)
155{
156   return (value + a - 1) & ~(a-1);
157}
158
159static void
160MeasureDownloadRate(void)
161{
162   const int w = TexWidth + 2 * TexBorder;
163   const int h = TexHeight + 2 * TexBorder;
164   const int image_bytes = align(w * h * BytesPerTexel(Format), ALIGN);
165   const int bytes = image_bytes * NR_TEXOBJ;
166   GLubyte *texImage;
167   GLdouble t0, t1, time;
168   int count;
169   int i;
170   int offset = 0;
171   GLdouble total = 0;		/* ints will tend to overflow */
172
173   printf("allocating %d bytes for %d %dx%d images\n",
174	  bytes, NR_TEXOBJ, w, h);
175
176#ifdef _WIN32
177   texImage = (GLubyte *) _aligned_malloc(bytes, ALIGN);
178#else
179   texImage = (GLubyte *) aligned_alloc(ALIGN, bytes);
180#endif
181   if (!texImage) {
182      DownloadRate = 0.0;
183      return;
184   }
185
186   printf("alloc %p\n", texImage);
187
188   for (i = 1; !(((unsigned long)texImage) & i); i<<=1)
189      ;
190   printf("texture image alignment: %d bytes (%p)\n", i, texImage);
191
192   for (i = 0; i < bytes; i++) {
193      texImage[i] = i & 0xff;
194   }
195
196   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
197   glPixelStorei(GL_PACK_ALIGNMENT, 1);
198
199   if (ScaleAndBias) {
200      glPixelTransferf(GL_RED_SCALE, 0.5);
201      glPixelTransferf(GL_GREEN_SCALE, 0.5);
202      glPixelTransferf(GL_BLUE_SCALE, 0.5);
203      glPixelTransferf(GL_RED_BIAS, 0.5);
204      glPixelTransferf(GL_GREEN_BIAS, 0.5);
205      glPixelTransferf(GL_BLUE_BIAS, 0.5);
206   }
207   else {
208      glPixelTransferf(GL_RED_SCALE, 1.0);
209      glPixelTransferf(GL_GREEN_SCALE, 1.0);
210      glPixelTransferf(GL_BLUE_SCALE, 1.0);
211      glPixelTransferf(GL_RED_BIAS, 0.0);
212      glPixelTransferf(GL_GREEN_BIAS, 0.0);
213      glPixelTransferf(GL_BLUE_BIAS, 0.0);
214   }
215
216   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
217   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
218   glEnable(GL_TEXTURE_2D);
219
220   count = 0;
221   t0 = glutGet(GLUT_ELAPSED_TIME) * 0.001;
222   do {
223      int img = count%NR_TEXOBJ;
224      GLubyte *img_ptr = texImage + img * image_bytes;
225
226      glBindTexture(GL_TEXTURE_2D, TexObj[img]);
227
228      if (SubImage && count > 0) {
229	 /* Only update a portion of the image each iteration.  This
230	  * is presumably why you'd want to use texsubimage, otherwise
231	  * you may as well just call teximage again.
232	  *
233	  * A bigger question is whether to use a pointer that moves
234	  * with each call, ie does the incoming data come from L2
235	  * cache under normal circumstances, or is it pulled from
236	  * uncached memory?
237	  *
238	  * There's a good argument to say L2 cache, ie you'd expect
239	  * the data to have been recently generated.  It's possible
240	  * that it could have come from a file read, which may or may
241	  * not have gone through the cpu.
242	  */
243         glTexSubImage2D(GL_TEXTURE_2D, 0,
244			 -TexBorder,
245			 -TexBorder + offset * h/8,
246			 w,
247			 h/8,
248                         FormatTable[Format].Format,
249                         FormatTable[Format].Type,
250#if 1
251			 texImage /* likely in L2$ */
252#else
253			 img_ptr + offset * bytes/8 /* unlikely in L2$ */
254#endif
255	    );
256	 offset += 1;
257	 offset %= 8;
258	 total += w * h / 8;
259      }
260      else {
261         glTexImage2D(GL_TEXTURE_2D, 0,
262                      FormatTable[Format].IntFormat, w, h, TexBorder,
263                      FormatTable[Format].Format,
264                      FormatTable[Format].Type,
265		      img_ptr);
266	 total += w*h;
267      }
268
269      /* draw a tiny polygon to force texture into texram */
270      glBegin(GL_TRIANGLES);
271      glTexCoord2f(0, 0);     glVertex2f(1, 1);
272      glTexCoord2f(1, 0);     glVertex2f(3, 1);
273      glTexCoord2f(0.5, 1);   glVertex2f(2, 3);
274      glEnd();
275
276      t1 = glutGet(GLUT_ELAPSED_TIME) * 0.001;
277      time = t1 - t0;
278      count++;
279   } while (time < 3.0);
280
281   glDisable(GL_TEXTURE_2D);
282
283   printf("total texels=%f  time=%f\n", total, time);
284   DownloadRate = total / time;
285
286
287#ifdef _WIN32
288   _aligned_free(texImage);
289#else
290   free(texImage);
291#endif
292
293   {
294      GLint err = glGetError();
295      if (err)
296         printf("GL error %d\n", err);
297   }
298}
299
300
301static void
302PrintString(const char *s)
303{
304   while (*s) {
305      glutBitmapCharacter(GLUT_BITMAP_8_BY_13, (int) *s);
306      s++;
307   }
308}
309
310
311static void
312Display(void)
313{
314   const int w = TexWidth + 2 * TexBorder;
315   const int h = TexHeight + 2 * TexBorder;
316   char s[1000];
317
318   glClear(GL_COLOR_BUFFER_BIT);
319
320   glRasterPos2i(10, 80);
321   sprintf(s, "Texture size[cursor]: %d x %d  Border[b]: %d", w, h, TexBorder);
322   PrintString(s);
323
324   glRasterPos2i(10, 65);
325   sprintf(s, "Format[f]: %s  Type: %s  IntFormat: %s",
326           FormatStr(FormatTable[Format].Format),
327           TypeStr(  FormatTable[Format].Type),
328           FormatStr(FormatTable[Format].IntFormat));
329   PrintString(s);
330
331   glRasterPos2i(10, 50);
332   sprintf(s, "Pixel Scale&Bias[p]: %s   TexSubImage[s]: %s",
333           ScaleAndBias ? "Yes" : "No",
334           SubImage ? "Yes" : "No");
335   PrintString(s);
336
337   if (Mode == 0) {
338      glRasterPos2i(200, 10);
339      sprintf(s, "...Measuring...");
340      PrintString(s);
341      glutSwapBuffers();
342      glutPostRedisplay();
343      Mode++;
344   }
345   else if (Mode == 1) {
346      MeasureDownloadRate();
347      glutPostRedisplay();
348      Mode++;
349   }
350   else {
351      /* show results */
352      glRasterPos2i(10, 10);
353      sprintf(s, "Download rate: %g Mtexels/second  %g MB/second",
354              DownloadRate / 1000000.0,
355              DownloadRate * BytesPerTexel(Format) / 1000000.0);
356      PrintString(s);
357      {
358         GLint r, g, b, a, l, i;
359         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_RED_SIZE, &r);
360         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_GREEN_SIZE, &g);
361         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_BLUE_SIZE, &b);
362         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_ALPHA_SIZE, &a);
363         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_LUMINANCE_SIZE, &l);
364         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTENSITY_SIZE, &i);
365         sprintf(s, "TexelBits: R=%d G=%d B=%d A=%d L=%d I=%d", r, g, b, a, l, i);
366         glRasterPos2i(10, 25);
367         PrintString(s);
368      }
369
370      glutSwapBuffers();
371   }
372}
373
374
375static void
376Reshape(int width, int height)
377{
378   glViewport( 0, 0, width, height );
379   glMatrixMode( GL_PROJECTION );
380   glLoadIdentity();
381   glOrtho(0, width, 0, height, -1, 1);
382   glMatrixMode( GL_MODELVIEW );
383   glLoadIdentity();
384}
385
386
387
388static void
389Key(unsigned char key, int x, int y)
390{
391   (void) x;
392   (void) y;
393   switch (key) {
394      case ' ':
395         Mode = 0;
396         break;
397      case 'b':
398         /* toggle border */
399         TexBorder = 1 - TexBorder;
400         Mode = 0;
401         break;
402      case 'f':
403         /* change format */
404         Format = (Format + 1) % NUM_FORMATS;
405         Mode = 0;
406         break;
407      case 'F':
408         /* change format */
409         Format = (Format - 1) % NUM_FORMATS;
410         Mode = 0;
411         break;
412      case 'p':
413         /* toggle border */
414         ScaleAndBias = !ScaleAndBias;
415         Mode = 0;
416         break;
417      case 's':
418         SubImage = !SubImage;
419         Mode = 0;
420         break;
421      case 27:
422         exit(0);
423         break;
424   }
425   glutPostRedisplay();
426}
427
428
429static void
430SpecialKey(int key, int x, int y)
431{
432   (void) x;
433   (void) y;
434   switch (key) {
435      case GLUT_KEY_UP:
436         if (TexHeight < MaxSize)
437            TexHeight *= 2;
438         break;
439      case GLUT_KEY_DOWN:
440         if (TexHeight > 1)
441            TexHeight /= 2;
442         break;
443      case GLUT_KEY_LEFT:
444         if (TexWidth > 1)
445            TexWidth /= 2;
446         break;
447      case GLUT_KEY_RIGHT:
448         if (TexWidth < MaxSize)
449            TexWidth *= 2;
450         break;
451   }
452   Mode = 0;
453   glutPostRedisplay();
454}
455
456
457static void
458Init(void)
459{
460   printf("GL_VENDOR = %s\n", (const char *) glGetString(GL_VENDOR));
461   printf("GL_VERSION = %s\n", (const char *) glGetString(GL_VERSION));
462   printf("GL_RENDERER = %s\n", (const char *) glGetString(GL_RENDERER));
463}
464
465
466int
467main(int argc, char *argv[])
468{
469   glutInit( &argc, argv );
470   glutInitWindowPosition( 0, 0 );
471   glutInitWindowSize( 600, 100 );
472   glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );
473   glutCreateWindow(argv[0]);
474   glutReshapeFunc( Reshape );
475   glutKeyboardFunc( Key );
476   glutSpecialFunc( SpecialKey );
477   glutDisplayFunc( Display );
478   Init();
479   glutMainLoop();
480   return 0;
481}
482