| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/tools/player_x11/x11_video_renderer.h" | 5 #include "media/tools/player_x11/x11_video_renderer.h" |
| 6 | 6 |
| 7 #include <dlfcn.h> | 7 #include <dlfcn.h> |
| 8 #include <X11/Xutil.h> | 8 #include <X11/Xutil.h> |
| 9 #include <X11/extensions/Xrender.h> | 9 #include <X11/extensions/Xrender.h> |
| 10 #include <X11/extensions/Xcomposite.h> | 10 #include <X11/extensions/Xcomposite.h> |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 52 | 52 |
| 53 return pictformat; | 53 return pictformat; |
| 54 } | 54 } |
| 55 | 55 |
| 56 X11VideoRenderer::X11VideoRenderer(Display* display, Window window) | 56 X11VideoRenderer::X11VideoRenderer(Display* display, Window window) |
| 57 : display_(display), | 57 : display_(display), |
| 58 window_(window), | 58 window_(window), |
| 59 image_(NULL), | 59 image_(NULL), |
| 60 new_frame_(false), | 60 new_frame_(false), |
| 61 picture_(0), | 61 picture_(0), |
| 62 use_render_(false), | 62 use_render_(false) { |
| 63 use_gl_(false), | |
| 64 gl_context_(NULL) { | |
| 65 // Save the instance of the video renderer. | |
| 66 CHECK(!instance_); | |
| 67 instance_ = this; | |
| 68 } | 63 } |
| 69 | 64 |
| 70 X11VideoRenderer::~X11VideoRenderer() { | 65 X11VideoRenderer::~X11VideoRenderer() { |
| 71 CHECK(instance_); | |
| 72 instance_ = NULL; | |
| 73 } | 66 } |
| 74 | 67 |
| 75 // static | 68 // static |
| 76 bool X11VideoRenderer::IsMediaFormatSupported( | 69 bool X11VideoRenderer::IsMediaFormatSupported( |
| 77 const media::MediaFormat& media_format) { | 70 const media::MediaFormat& media_format) { |
| 78 int width = 0; | 71 int width = 0; |
| 79 int height = 0; | 72 int height = 0; |
| 80 return ParseMediaFormat(media_format, &width, &height); | 73 return ParseMediaFormat(media_format, &width, &height); |
| 81 } | 74 } |
| 82 | 75 |
| 83 void X11VideoRenderer::OnStop() { | 76 void X11VideoRenderer::OnStop() { |
| 84 if (use_gl_) { | |
| 85 glXMakeCurrent(display_, 0, NULL); | |
| 86 glXDestroyContext(display_, gl_context_); | |
| 87 } | |
| 88 if (image_) { | 77 if (image_) { |
| 89 XDestroyImage(image_); | 78 XDestroyImage(image_); |
| 90 } | 79 } |
| 91 if (use_render_) { | 80 XRenderFreePicture(display_, picture_); |
| 92 XRenderFreePicture(display_, picture_); | |
| 93 } | |
| 94 } | 81 } |
| 95 | 82 |
| 96 static GLXContext InitGLContext(Display* display, Window window) { | |
| 97 // Some versions of NVIDIA's GL libGL.so include a broken version of | |
| 98 // dlopen/dlsym, and so linking it into chrome breaks it. So we dynamically | |
| 99 // load it, and use glew to dynamically resolve symbols. | |
| 100 // See http://code.google.com/p/chromium/issues/detail?id=16800 | |
| 101 void* handle = dlopen("libGL.so.1", RTLD_LAZY | RTLD_GLOBAL); | |
| 102 if (!handle) { | |
| 103 LOG(ERROR) << "Could not find libGL.so.1"; | |
| 104 return NULL; | |
| 105 } | |
| 106 if (glxewInit() != GLEW_OK) { | |
| 107 LOG(ERROR) << "GLXEW failed initialization"; | |
| 108 return NULL; | |
| 109 } | |
| 110 | |
| 111 XWindowAttributes attributes; | |
| 112 XGetWindowAttributes(display, window, &attributes); | |
| 113 XVisualInfo visual_info_template; | |
| 114 visual_info_template.visualid = XVisualIDFromVisual(attributes.visual); | |
| 115 int visual_info_count = 0; | |
| 116 XVisualInfo* visual_info_list = XGetVisualInfo(display, VisualIDMask, | |
| 117 &visual_info_template, | |
| 118 &visual_info_count); | |
| 119 GLXContext context = NULL; | |
| 120 for (int i = 0; i < visual_info_count && !context; ++i) { | |
| 121 context = glXCreateContext(display, visual_info_list + i, 0, | |
| 122 True /* Direct rendering */); | |
| 123 } | |
| 124 | |
| 125 XFree(visual_info_list); | |
| 126 if (!context) { | |
| 127 return NULL; | |
| 128 } | |
| 129 | |
| 130 if (!glXMakeCurrent(display, window, context)) { | |
| 131 glXDestroyContext(display, context); | |
| 132 return NULL; | |
| 133 } | |
| 134 | |
| 135 if (glewInit() != GLEW_OK) { | |
| 136 LOG(ERROR) << "GLEW failed initialization"; | |
| 137 glXDestroyContext(display, context); | |
| 138 return NULL; | |
| 139 } | |
| 140 | |
| 141 if (!glewIsSupported("GL_VERSION_2_0")) { | |
| 142 LOG(ERROR) << "GL implementation doesn't support GL version 2.0"; | |
| 143 glXDestroyContext(display, context); | |
| 144 return NULL; | |
| 145 } | |
| 146 | |
| 147 return context; | |
| 148 } | |
| 149 | |
| 150 // Matrix used for the YUV to RGB conversion. | |
| 151 static const float kYUV2RGB[9] = { | |
| 152 1.f, 0.f, 1.403f, | |
| 153 1.f, -.344f, -.714f, | |
| 154 1.f, 1.772f, 0.f, | |
| 155 }; | |
| 156 | |
| 157 // Vertices for a full screen quad. | |
| 158 static const float kVertices[8] = { | |
| 159 -1.f, 1.f, | |
| 160 -1.f, -1.f, | |
| 161 1.f, 1.f, | |
| 162 1.f, -1.f, | |
| 163 }; | |
| 164 | |
| 165 // Texture Coordinates mapping the entire texture. | |
| 166 static const float kTextureCoords[8] = { | |
| 167 0, 0, | |
| 168 0, 1, | |
| 169 1, 0, | |
| 170 1, 1, | |
| 171 }; | |
| 172 | |
| 173 // Pass-through vertex shader. | |
| 174 static const char kVertexShader[] = | |
| 175 "varying vec2 interp_tc;\n" | |
| 176 "\n" | |
| 177 "attribute vec4 in_pos;\n" | |
| 178 "attribute vec2 in_tc;\n" | |
| 179 "\n" | |
| 180 "void main() {\n" | |
| 181 " interp_tc = in_tc;\n" | |
| 182 " gl_Position = in_pos;\n" | |
| 183 "}\n"; | |
| 184 | |
| 185 // YUV to RGB pixel shader. Loads a pixel from each plane and pass through the | |
| 186 // matrix. | |
| 187 static const char kFragmentShader[] = | |
| 188 "varying vec2 interp_tc;\n" | |
| 189 "\n" | |
| 190 "uniform sampler2D y_tex;\n" | |
| 191 "uniform sampler2D u_tex;\n" | |
| 192 "uniform sampler2D v_tex;\n" | |
| 193 "uniform mat3 yuv2rgb;\n" | |
| 194 "\n" | |
| 195 "void main() {\n" | |
| 196 " float y = texture2D(y_tex, interp_tc).x;\n" | |
| 197 " float u = texture2D(u_tex, interp_tc).r - .5;\n" | |
| 198 " float v = texture2D(v_tex, interp_tc).r - .5;\n" | |
| 199 " vec3 rgb = yuv2rgb * vec3(y, u, v);\n" | |
| 200 " gl_FragColor = vec4(rgb, 1);\n" | |
| 201 "}\n"; | |
| 202 | |
| 203 // Buffer size for compile errors. | |
| 204 static const unsigned int kErrorSize = 4096; | |
| 205 | |
| 206 bool X11VideoRenderer::OnInitialize(media::VideoDecoder* decoder) { | 83 bool X11VideoRenderer::OnInitialize(media::VideoDecoder* decoder) { |
| 207 if (!ParseMediaFormat(decoder->media_format(), &width_, &height_)) | 84 if (!ParseMediaFormat(decoder->media_format(), &width_, &height_)) |
| 208 return false; | 85 return false; |
| 209 | 86 |
| 87 LOG(INFO) << "Initializing X11 Renderer..."; |
| 88 |
| 210 // Resize the window to fit that of the video. | 89 // Resize the window to fit that of the video. |
| 211 XResizeWindow(display_, window_, width_, height_); | 90 XResizeWindow(display_, window_, width_, height_); |
| 212 | 91 |
| 213 gl_context_ = InitGLContext(display_, window_); | 92 // Testing XRender support. We'll use the very basic of XRender |
| 214 use_gl_ = (gl_context_ != NULL); | 93 // so if it presents it is already good enough. We don't need |
| 94 // to check its version. |
| 95 int dummy; |
| 96 use_render_ = XRenderQueryExtension(display_, &dummy, &dummy); |
| 215 | 97 |
| 216 if (use_gl_) { | 98 if (use_render_) { |
| 217 glMatrixMode(GL_MODELVIEW); | 99 // If we are using XRender, we'll create a picture representing the |
| 218 glLoadIdentity(); | 100 // window. |
| 219 glViewport(0, 0, width_, height_); | 101 XWindowAttributes attr; |
| 102 XGetWindowAttributes(display_, window_, &attr); |
| 220 | 103 |
| 221 // Create 3 textures, one for each plane, and bind them to different | 104 XRenderPictFormat* pictformat = XRenderFindVisualFormat( |
| 222 // texture units. | 105 display_, |
| 223 glGenTextures(media::VideoSurface::kNumYUVPlanes, textures_); | 106 attr.visual); |
| 224 glActiveTexture(GL_TEXTURE0); | 107 CHECK(pictformat) << "XRENDER does not support default visual"; |
| 225 glBindTexture(GL_TEXTURE_2D, textures_[0]); | |
| 226 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| 227 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| 228 glEnable(GL_TEXTURE_2D); | |
| 229 | 108 |
| 230 glActiveTexture(GL_TEXTURE1); | 109 picture_ = XRenderCreatePicture(display_, window_, pictformat, 0, NULL); |
| 231 glBindTexture(GL_TEXTURE_2D, textures_[1]); | 110 CHECK(picture_) << "Backing picture not created"; |
| 232 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | 111 } |
| 233 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| 234 glEnable(GL_TEXTURE_2D); | |
| 235 | 112 |
| 236 glActiveTexture(GL_TEXTURE2); | 113 // Initialize the XImage to store the output of YUV -> RGB conversion. |
| 237 glBindTexture(GL_TEXTURE_2D, textures_[2]); | 114 image_ = XCreateImage(display_, |
| 238 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | 115 DefaultVisual(display_, DefaultScreen(display_)), |
| 239 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | 116 DefaultDepth(display_, DefaultScreen(display_)), |
| 240 glEnable(GL_TEXTURE_2D); | 117 ZPixmap, |
| 118 0, |
| 119 static_cast<char*>(malloc(width_ * height_ * 4)), |
| 120 width_, |
| 121 height_, |
| 122 32, |
| 123 width_ * 4); |
| 124 DCHECK(image_); |
| 241 | 125 |
| 242 GLuint program_ = glCreateProgram(); | 126 // Save this instance. |
| 243 | 127 DCHECK(!instance_); |
| 244 // Create our YUV->RGB shader. | 128 instance_ = this; |
| 245 GLuint vertex_shader_ = glCreateShader(GL_VERTEX_SHADER); | |
| 246 const char* vs_source = kVertexShader; | |
| 247 int vs_size = sizeof(kVertexShader); | |
| 248 glShaderSource(vertex_shader_, 1, &vs_source, &vs_size); | |
| 249 glCompileShader(vertex_shader_); | |
| 250 int result = GL_FALSE; | |
| 251 glGetShaderiv(vertex_shader_, GL_COMPILE_STATUS, &result); | |
| 252 if (!result) { | |
| 253 char log[kErrorSize]; | |
| 254 int len; | |
| 255 glGetShaderInfoLog(vertex_shader_, kErrorSize - 1, &len, log); | |
| 256 log[kErrorSize - 1] = 0; | |
| 257 LOG(FATAL) << log; | |
| 258 } | |
| 259 glAttachShader(program_, vertex_shader_); | |
| 260 | |
| 261 GLuint fragment_shader_ = glCreateShader(GL_FRAGMENT_SHADER); | |
| 262 const char* ps_source = kFragmentShader; | |
| 263 int ps_size = sizeof(kFragmentShader); | |
| 264 glShaderSource(fragment_shader_, 1, &ps_source, &ps_size); | |
| 265 glCompileShader(fragment_shader_); | |
| 266 result = GL_FALSE; | |
| 267 glGetShaderiv(fragment_shader_, GL_COMPILE_STATUS, &result); | |
| 268 if (!result) { | |
| 269 char log[kErrorSize]; | |
| 270 int len; | |
| 271 glGetShaderInfoLog(fragment_shader_, kErrorSize - 1, &len, log); | |
| 272 log[kErrorSize - 1] = 0; | |
| 273 LOG(FATAL) << log; | |
| 274 } | |
| 275 glAttachShader(program_, fragment_shader_); | |
| 276 | |
| 277 glLinkProgram(program_); | |
| 278 result = GL_FALSE; | |
| 279 glGetProgramiv(program_, GL_LINK_STATUS, &result); | |
| 280 if (!result) { | |
| 281 char log[kErrorSize]; | |
| 282 int len; | |
| 283 glGetProgramInfoLog(program_, kErrorSize - 1, &len, log); | |
| 284 log[kErrorSize - 1] = 0; | |
| 285 LOG(FATAL) << log; | |
| 286 } | |
| 287 glUseProgram(program_); | |
| 288 | |
| 289 // Bind parameters. | |
| 290 glUniform1i(glGetUniformLocation(program_, "y_tex"), 0); | |
| 291 glUniform1i(glGetUniformLocation(program_, "u_tex"), 1); | |
| 292 glUniform1i(glGetUniformLocation(program_, "v_tex"), 2); | |
| 293 int yuv2rgb_location = glGetUniformLocation(program_, "yuv2rgb"); | |
| 294 glUniformMatrix3fv(yuv2rgb_location, 1, GL_TRUE, kYUV2RGB); | |
| 295 | |
| 296 int pos_location = glGetAttribLocation(program_, "in_pos"); | |
| 297 glEnableVertexAttribArray(pos_location); | |
| 298 glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices); | |
| 299 | |
| 300 int tc_location = glGetAttribLocation(program_, "in_tc"); | |
| 301 glEnableVertexAttribArray(tc_location); | |
| 302 glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, | |
| 303 kTextureCoords); | |
| 304 | |
| 305 // We are getting called on a thread. Release the context so that it can be | |
| 306 // made current on the main thread. | |
| 307 glXMakeCurrent(display_, 0, NULL); | |
| 308 } else { | |
| 309 // Testing XRender support. We'll use the very basic of XRender | |
| 310 // so if it presents it is already good enough. We don't need | |
| 311 // to check its version. | |
| 312 int dummy; | |
| 313 use_render_ = XRenderQueryExtension(display_, &dummy, &dummy); | |
| 314 | |
| 315 if (use_render_) { | |
| 316 // If we are using XRender, we'll create a picture representing the | |
| 317 // window. | |
| 318 XWindowAttributes attr; | |
| 319 XGetWindowAttributes(display_, window_, &attr); | |
| 320 | |
| 321 XRenderPictFormat* pictformat = XRenderFindVisualFormat( | |
| 322 display_, | |
| 323 attr.visual); | |
| 324 CHECK(pictformat) << "XRENDER does not support default visual"; | |
| 325 | |
| 326 picture_ = XRenderCreatePicture(display_, window_, pictformat, 0, NULL); | |
| 327 CHECK(picture_) << "Backing picture not created"; | |
| 328 } | |
| 329 | |
| 330 // Initialize the XImage to store the output of YUV -> RGB conversion. | |
| 331 image_ = XCreateImage(display_, | |
| 332 DefaultVisual(display_, DefaultScreen(display_)), | |
| 333 DefaultDepth(display_, DefaultScreen(display_)), | |
| 334 ZPixmap, | |
| 335 0, | |
| 336 static_cast<char*>(malloc(width_ * height_ * 4)), | |
| 337 width_, | |
| 338 height_, | |
| 339 32, | |
| 340 width_ * 4); | |
| 341 DCHECK(image_); | |
| 342 } | |
| 343 return true; | 129 return true; |
| 344 } | 130 } |
| 345 | 131 |
| 346 void X11VideoRenderer::OnFrameAvailable() { | 132 void X11VideoRenderer::OnFrameAvailable() { |
| 347 AutoLock auto_lock(lock_); | 133 AutoLock auto_lock(lock_); |
| 348 new_frame_ = true; | 134 new_frame_ = true; |
| 349 } | 135 } |
| 350 | 136 |
| 351 void X11VideoRenderer::Paint() { | 137 void X11VideoRenderer::Paint() { |
| 352 // Use |new_frame_| to prevent overdraw since Paint() is called more | 138 // Use |new_frame_| to prevent overdraw since Paint() is called more |
| 353 // often than needed. It is OK to lock only this flag and we don't | 139 // often than needed. It is OK to lock only this flag and we don't |
| 354 // want to lock the whole function because this method takes a long | 140 // want to lock the whole function because this method takes a long |
| 355 // time to complete. | 141 // time to complete. |
| 356 { | 142 { |
| 357 AutoLock auto_lock(lock_); | 143 AutoLock auto_lock(lock_); |
| 358 if (!new_frame_) | 144 if (!new_frame_) |
| 359 return; | 145 return; |
| 360 new_frame_ = false; | 146 new_frame_ = false; |
| 361 } | 147 } |
| 362 | 148 |
| 363 scoped_refptr<media::VideoFrame> video_frame; | 149 scoped_refptr<media::VideoFrame> video_frame; |
| 364 GetCurrentFrame(&video_frame); | 150 GetCurrentFrame(&video_frame); |
| 365 | 151 |
| 366 if ((!use_gl_ && !image_) || !video_frame) | 152 if (!image_ ||!video_frame) |
| 367 return; | 153 return; |
| 368 | 154 |
| 369 // Convert YUV frame to RGB. | 155 // Convert YUV frame to RGB. |
| 370 media::VideoSurface frame_in; | 156 media::VideoSurface frame_in; |
| 371 if (video_frame->Lock(&frame_in)) { | 157 if (video_frame->Lock(&frame_in)) { |
| 372 DCHECK(frame_in.format == media::VideoSurface::YV12 || | 158 DCHECK(frame_in.format == media::VideoSurface::YV12 || |
| 373 frame_in.format == media::VideoSurface::YV16); | 159 frame_in.format == media::VideoSurface::YV16); |
| 374 DCHECK(frame_in.strides[media::VideoSurface::kUPlane] == | 160 DCHECK(frame_in.strides[media::VideoSurface::kUPlane] == |
| 375 frame_in.strides[media::VideoSurface::kVPlane]); | 161 frame_in.strides[media::VideoSurface::kVPlane]); |
| 376 DCHECK(frame_in.planes == media::VideoSurface::kNumYUVPlanes); | 162 DCHECK(frame_in.planes == media::VideoSurface::kNumYUVPlanes); |
| 377 | 163 |
| 378 if (use_gl_) { | 164 DCHECK(image_->data); |
| 379 if (glXGetCurrentContext() != gl_context_ || | 165 media::YUVType yuv_type = (frame_in.format == media::VideoSurface::YV12) ? |
| 380 glXGetCurrentDrawable() != window_) { | 166 media::YV12 : media::YV16; |
| 381 glXMakeCurrent(display_, window_, gl_context_); | 167 media::ConvertYUVToRGB32(frame_in.data[media::VideoSurface::kYPlane], |
| 382 } | 168 frame_in.data[media::VideoSurface::kUPlane], |
| 383 for (unsigned int i = 0; i < media::VideoSurface::kNumYUVPlanes; ++i) { | 169 frame_in.data[media::VideoSurface::kVPlane], |
| 384 unsigned int width = (i == media::VideoSurface::kYPlane) ? | 170 (uint8*)image_->data, |
| 385 frame_in.width : frame_in.width / 2; | 171 frame_in.width, |
| 386 unsigned int height = (i == media::VideoSurface::kYPlane || | 172 frame_in.height, |
| 387 frame_in.format == media::VideoSurface::YV16) ? | 173 frame_in.strides[media::VideoSurface::kYPlane], |
| 388 frame_in.height : frame_in.height / 2; | 174 frame_in.strides[media::VideoSurface::kUPlane], |
| 389 glActiveTexture(GL_TEXTURE0 + i); | 175 image_->bytes_per_line, |
| 390 glPixelStorei(GL_UNPACK_ROW_LENGTH, frame_in.strides[i]); | 176 yuv_type); |
| 391 glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, | |
| 392 GL_LUMINANCE, GL_UNSIGNED_BYTE, frame_in.data[i]); | |
| 393 } | |
| 394 } else { | |
| 395 DCHECK(image_->data); | |
| 396 media::YUVType yuv_type = (frame_in.format == media::VideoSurface::YV12) ? | |
| 397 media::YV12 : media::YV16; | |
| 398 media::ConvertYUVToRGB32(frame_in.data[media::VideoSurface::kYPlane], | |
| 399 frame_in.data[media::VideoSurface::kUPlane], | |
| 400 frame_in.data[media::VideoSurface::kVPlane], | |
| 401 (uint8*)image_->data, | |
| 402 frame_in.width, | |
| 403 frame_in.height, | |
| 404 frame_in.strides[media::VideoSurface::kYPlane], | |
| 405 frame_in.strides[media::VideoSurface::kUPlane], | |
| 406 image_->bytes_per_line, | |
| 407 yuv_type); | |
| 408 } | |
| 409 video_frame->Unlock(); | 177 video_frame->Unlock(); |
| 410 } else { | 178 } else { |
| 411 NOTREACHED(); | 179 NOTREACHED(); |
| 412 } | 180 } |
| 413 | 181 |
| 414 if (use_gl_) { | |
| 415 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | |
| 416 glXSwapBuffers(display_, window_); | |
| 417 return; | |
| 418 } | |
| 419 | |
| 420 if (use_render_) { | 182 if (use_render_) { |
| 421 // If XRender is used, we'll upload the image to a pixmap. And then | 183 // If XRender is used, we'll upload the image to a pixmap. And then |
| 422 // creats a picture from the pixmap and composite the picture over | 184 // creats a picture from the pixmap and composite the picture over |
| 423 // the picture represending the window. | 185 // the picture represending the window. |
| 424 | 186 |
| 425 // Creates a XImage. | 187 // Creates a XImage. |
| 426 XImage image; | 188 XImage image; |
| 427 memset(&image, 0, sizeof(image)); | 189 memset(&image, 0, sizeof(image)); |
| 428 image.width = width_; | 190 image.width = width_; |
| 429 image.height = height_; | 191 image.height = height_; |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 468 // If XRender is not used, simply put the image to the server. | 230 // If XRender is not used, simply put the image to the server. |
| 469 // This will have a tearing effect but this is OK. | 231 // This will have a tearing effect but this is OK. |
| 470 // TODO(hclam): Upload the image to a pixmap and do XCopyArea() | 232 // TODO(hclam): Upload the image to a pixmap and do XCopyArea() |
| 471 // to the window. | 233 // to the window. |
| 472 GC gc = XCreateGC(display_, window_, 0, NULL); | 234 GC gc = XCreateGC(display_, window_, 0, NULL); |
| 473 XPutImage(display_, window_, gc, image_, | 235 XPutImage(display_, window_, gc, image_, |
| 474 0, 0, 0, 0, width_, height_); | 236 0, 0, 0, 0, width_, height_); |
| 475 XFlush(display_); | 237 XFlush(display_); |
| 476 XFreeGC(display_, gc); | 238 XFreeGC(display_, gc); |
| 477 } | 239 } |
| OLD | NEW |