| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #ifndef CONTENT_COMMON_GPU_MEDIA_DXVA_VIDEO_DECODE_ACCELERATOR_H_ | |
| 6 #define CONTENT_COMMON_GPU_MEDIA_DXVA_VIDEO_DECODE_ACCELERATOR_H_ | |
| 7 | |
| 8 #include <d3d11.h> | |
| 9 #include <d3d9.h> | |
| 10 #include <initguid.h> | |
| 11 #include <stdint.h> | |
| 12 // Work around bug in this header by disabling the relevant warning for it. | |
| 13 // https://connect.microsoft.com/VisualStudio/feedback/details/911260/dxva2api-h
-in-win8-sdk-triggers-c4201-with-w4 | |
| 14 #pragma warning(push) | |
| 15 #pragma warning(disable:4201) | |
| 16 #include <dxva2api.h> | |
| 17 #pragma warning(pop) | |
| 18 #include <mfidl.h> | |
| 19 | |
| 20 #include <list> | |
| 21 #include <map> | |
| 22 #include <vector> | |
| 23 | |
| 24 #include "base/compiler_specific.h" | |
| 25 #include "base/macros.h" | |
| 26 #include "base/memory/linked_ptr.h" | |
| 27 #include "base/memory/weak_ptr.h" | |
| 28 #include "base/synchronization/lock.h" | |
| 29 #include "base/threading/non_thread_safe.h" | |
| 30 #include "base/threading/thread.h" | |
| 31 #include "base/win/scoped_comptr.h" | |
| 32 #include "content/common/content_export.h" | |
| 33 #include "content/common/gpu/media/gpu_video_decode_accelerator_helpers.h" | |
| 34 #include "media/filters/h264_parser.h" | |
| 35 #include "media/video/video_decode_accelerator.h" | |
| 36 | |
| 37 interface IMFSample; | |
| 38 interface IDirect3DSurface9; | |
| 39 | |
| 40 namespace gfx { | |
| 41 class GLContext; | |
| 42 } | |
| 43 | |
| 44 typedef HRESULT (WINAPI* CreateDXGIDeviceManager)( | |
| 45 UINT* reset_token, | |
| 46 IMFDXGIDeviceManager** device_manager); | |
| 47 | |
| 48 namespace content { | |
| 49 | |
| 50 // Provides functionality to detect H.264 stream configuration changes. | |
| 51 // TODO(ananta) | |
| 52 // Move this to a common place so that all VDA's can use this. | |
| 53 class H264ConfigChangeDetector { | |
| 54 public: | |
| 55 H264ConfigChangeDetector(); | |
| 56 ~H264ConfigChangeDetector(); | |
| 57 | |
| 58 // Detects stream configuration changes. | |
| 59 // Returns false on failure. | |
| 60 bool DetectConfig(const uint8_t* stream, unsigned int size); | |
| 61 | |
| 62 bool config_changed() const { | |
| 63 return config_changed_; | |
| 64 } | |
| 65 | |
| 66 private: | |
| 67 // These fields are used to track the SPS/PPS in the H.264 bitstream and | |
| 68 // are eventually compared against the SPS/PPS in the bitstream to detect | |
| 69 // a change. | |
| 70 int last_sps_id_; | |
| 71 std::vector<uint8_t> last_sps_; | |
| 72 int last_pps_id_; | |
| 73 std::vector<uint8_t> last_pps_; | |
| 74 // Set to true if we detect a stream configuration change. | |
| 75 bool config_changed_; | |
| 76 // We want to indicate configuration changes only after we see IDR slices. | |
| 77 // This flag tracks that we potentially have a configuration change which | |
| 78 // we want to honor after we see an IDR slice. | |
| 79 bool pending_config_changed_; | |
| 80 | |
| 81 std::unique_ptr<media::H264Parser> parser_; | |
| 82 | |
| 83 DISALLOW_COPY_AND_ASSIGN(H264ConfigChangeDetector); | |
| 84 }; | |
| 85 | |
| 86 | |
| 87 // Class to provide a DXVA 2.0 based accelerator using the Microsoft Media | |
| 88 // foundation APIs via the VideoDecodeAccelerator interface. | |
| 89 // This class lives on a single thread and DCHECKs that it is never accessed | |
| 90 // from any other. | |
| 91 class CONTENT_EXPORT DXVAVideoDecodeAccelerator | |
| 92 : public media::VideoDecodeAccelerator { | |
| 93 public: | |
| 94 enum State { | |
| 95 kUninitialized, // un-initialized. | |
| 96 kNormal, // normal playing state. | |
| 97 kResetting, // upon received Reset(), before ResetDone() | |
| 98 kStopped, // upon output EOS received. | |
| 99 kFlushing, // upon flush request received. | |
| 100 kConfigChange, // stream configuration change detected. | |
| 101 }; | |
| 102 | |
| 103 // Does not take ownership of |client| which must outlive |*this|. | |
| 104 DXVAVideoDecodeAccelerator( | |
| 105 const GetGLContextCallback& get_gl_context_cb, | |
| 106 const MakeGLContextCurrentCallback& make_context_current_cb, | |
| 107 bool enable_accelerated_vpx_decode); | |
| 108 ~DXVAVideoDecodeAccelerator() override; | |
| 109 | |
| 110 // media::VideoDecodeAccelerator implementation. | |
| 111 bool Initialize(const Config& config, Client* client) override; | |
| 112 void Decode(const media::BitstreamBuffer& bitstream_buffer) override; | |
| 113 void AssignPictureBuffers( | |
| 114 const std::vector<media::PictureBuffer>& buffers) override; | |
| 115 void ReusePictureBuffer(int32_t picture_buffer_id) override; | |
| 116 void Flush() override; | |
| 117 void Reset() override; | |
| 118 void Destroy() override; | |
| 119 bool TryToSetupDecodeOnSeparateThread( | |
| 120 const base::WeakPtr<Client>& decode_client, | |
| 121 const scoped_refptr<base::SingleThreadTaskRunner>& decode_task_runner) | |
| 122 override; | |
| 123 GLenum GetSurfaceInternalFormat() const override; | |
| 124 | |
| 125 static media::VideoDecodeAccelerator::SupportedProfiles | |
| 126 GetSupportedProfiles(); | |
| 127 | |
| 128 // Preload dlls required for decoding. | |
| 129 static void PreSandboxInitialization(); | |
| 130 | |
| 131 private: | |
| 132 typedef void* EGLConfig; | |
| 133 typedef void* EGLSurface; | |
| 134 | |
| 135 // Returns the minimum resolution for the |profile| passed in. | |
| 136 static std::pair<int, int> GetMinResolution( | |
| 137 const media::VideoCodecProfile profile); | |
| 138 | |
| 139 // Returns the maximum resolution for the |profile| passed in. | |
| 140 static std::pair<int, int> GetMaxResolution( | |
| 141 const media::VideoCodecProfile profile); | |
| 142 | |
| 143 // Returns the maximum resolution for H264 video. | |
| 144 static std::pair<int, int> GetMaxH264Resolution(); | |
| 145 | |
| 146 // Certain AMD GPU drivers like R600, R700, Evergreen and Cayman and | |
| 147 // some second generation Intel GPU drivers crash if we create a video | |
| 148 // device with a resolution higher then 1920 x 1088. This function | |
| 149 // checks if the GPU is in this list and if yes returns true. | |
| 150 static bool IsLegacyGPU(ID3D11Device* device); | |
| 151 | |
| 152 // Creates and initializes an instance of the D3D device and the | |
| 153 // corresponding device manager. The device manager instance is eventually | |
| 154 // passed to the IMFTransform interface implemented by the decoder. | |
| 155 bool CreateD3DDevManager(); | |
| 156 | |
| 157 // Creates and initializes an instance of the DX11 device and the | |
| 158 // corresponding device manager. The device manager instance is eventually | |
| 159 // passed to the IMFTransform interface implemented by the decoder. | |
| 160 bool CreateDX11DevManager(); | |
| 161 | |
| 162 // Creates, initializes and sets the media codec types for the decoder. | |
| 163 bool InitDecoder(media::VideoCodecProfile profile); | |
| 164 | |
| 165 // Validates whether the decoder supports hardware video acceleration. | |
| 166 bool CheckDecoderDxvaSupport(); | |
| 167 | |
| 168 // Returns information about the input and output streams. This includes | |
| 169 // alignment information, decoder support flags, minimum sample size, etc. | |
| 170 bool GetStreamsInfoAndBufferReqs(); | |
| 171 | |
| 172 // Registers the input and output media types on the decoder. This includes | |
| 173 // the expected input and output formats. | |
| 174 bool SetDecoderMediaTypes(); | |
| 175 | |
| 176 // Registers the input media type for the decoder. | |
| 177 bool SetDecoderInputMediaType(); | |
| 178 | |
| 179 // Registers the output media type for the decoder. | |
| 180 bool SetDecoderOutputMediaType(const GUID& subtype); | |
| 181 | |
| 182 // Passes a command message to the decoder. This includes commands like | |
| 183 // start of stream, end of stream, flush, drain the decoder, etc. | |
| 184 bool SendMFTMessage(MFT_MESSAGE_TYPE msg, int32_t param); | |
| 185 | |
| 186 // The bulk of the decoding happens here. This function handles errors, | |
| 187 // format changes and processes decoded output. | |
| 188 void DoDecode(); | |
| 189 | |
| 190 // Invoked when we have a valid decoded output sample. Retrieves the D3D | |
| 191 // surface and maintains a copy of it which is passed eventually to the | |
| 192 // client when we have a picture buffer to copy the surface contents to. | |
| 193 bool ProcessOutputSample(IMFSample* sample); | |
| 194 | |
| 195 // Processes pending output samples by copying them to available picture | |
| 196 // slots. | |
| 197 void ProcessPendingSamples(); | |
| 198 | |
| 199 // Helper function to notify the accelerator client about the error. | |
| 200 void StopOnError(media::VideoDecodeAccelerator::Error error); | |
| 201 | |
| 202 // Transitions the decoder to the uninitialized state. The decoder will stop | |
| 203 // accepting requests in this state. | |
| 204 void Invalidate(); | |
| 205 | |
| 206 // Notifies the client that the input buffer identifed by input_buffer_id has | |
| 207 // been processed. | |
| 208 void NotifyInputBufferRead(int input_buffer_id); | |
| 209 | |
| 210 // Notifies the client that the decoder was flushed. | |
| 211 void NotifyFlushDone(); | |
| 212 | |
| 213 // Notifies the client that the decoder was reset. | |
| 214 void NotifyResetDone(); | |
| 215 | |
| 216 // Requests picture buffers from the client. | |
| 217 void RequestPictureBuffers(int width, int height); | |
| 218 | |
| 219 // Notifies the client about the availability of a picture. | |
| 220 void NotifyPictureReady(int picture_buffer_id, | |
| 221 int input_buffer_id); | |
| 222 | |
| 223 // Sends pending input buffer processed acks to the client if we don't have | |
| 224 // output samples waiting to be processed. | |
| 225 void NotifyInputBuffersDropped(); | |
| 226 | |
| 227 // Decodes pending input buffers. | |
| 228 void DecodePendingInputBuffers(); | |
| 229 | |
| 230 // Helper for handling the Flush operation. | |
| 231 void FlushInternal(); | |
| 232 | |
| 233 // Helper for handling the Decode operation. | |
| 234 void DecodeInternal(const base::win::ScopedComPtr<IMFSample>& input_sample); | |
| 235 | |
| 236 // Handles mid stream resolution changes. | |
| 237 void HandleResolutionChanged(int width, int height); | |
| 238 | |
| 239 struct DXVAPictureBuffer; | |
| 240 typedef std::map<int32_t, linked_ptr<DXVAPictureBuffer>> OutputBuffers; | |
| 241 | |
| 242 // Tells the client to dismiss the stale picture buffers passed in. | |
| 243 void DismissStaleBuffers(bool force); | |
| 244 | |
| 245 // Called after the client indicates we can recycle a stale picture buffer. | |
| 246 void DeferredDismissStaleBuffer(int32_t picture_buffer_id); | |
| 247 | |
| 248 // Sets the state of the decoder. Called from the main thread and the decoder | |
| 249 // thread. The state is changed on the main thread. | |
| 250 void SetState(State state); | |
| 251 | |
| 252 // Gets the state of the decoder. Can be called from the main thread and | |
| 253 // the decoder thread. Thread safe. | |
| 254 State GetState(); | |
| 255 | |
| 256 // Starts the thread used for decoding. | |
| 257 void StartDecoderThread(); | |
| 258 | |
| 259 // Returns if we have output samples waiting to be processed. We only | |
| 260 // allow one output sample to be present in the output queue at any given | |
| 261 // time. | |
| 262 bool OutputSamplesPresent(); | |
| 263 | |
| 264 // Copies the source surface |src_surface| to the destination |dest_surface|. | |
| 265 // The copying is done on the decoder thread. | |
| 266 void CopySurface(IDirect3DSurface9* src_surface, | |
| 267 IDirect3DSurface9* dest_surface, | |
| 268 int picture_buffer_id, | |
| 269 int input_buffer_id); | |
| 270 | |
| 271 // This is a notification that the source surface |src_surface| was copied to | |
| 272 // the destination |dest_surface|. Received on the main thread. | |
| 273 void CopySurfaceComplete(IDirect3DSurface9* src_surface, | |
| 274 IDirect3DSurface9* dest_surface, | |
| 275 int picture_buffer_id, | |
| 276 int input_buffer_id); | |
| 277 | |
| 278 // Copies the source texture |src_texture| to the destination |dest_texture|. | |
| 279 // The copying is done on the decoder thread. The |video_frame| parameter | |
| 280 // is the sample containing the frame to be copied. | |
| 281 void CopyTexture(ID3D11Texture2D* src_texture, | |
| 282 ID3D11Texture2D* dest_texture, | |
| 283 base::win::ScopedComPtr<IDXGIKeyedMutex> dest_keyed_mutex, | |
| 284 uint64_t keyed_mutex_value, | |
| 285 IMFSample* video_frame, | |
| 286 int picture_buffer_id, | |
| 287 int input_buffer_id); | |
| 288 | |
| 289 // Flushes the decoder device to ensure that the decoded surface is copied | |
| 290 // to the target surface. |iterations| helps to maintain an upper limit on | |
| 291 // the number of times we try to complete the flush operation. | |
| 292 void FlushDecoder(int iterations, | |
| 293 IDirect3DSurface9* src_surface, | |
| 294 IDirect3DSurface9* dest_surface, | |
| 295 int picture_buffer_id, | |
| 296 int input_buffer_id); | |
| 297 | |
| 298 // Polls to wait for GPU commands to be finished on the picture buffer | |
| 299 // before reusing it. | |
| 300 void WaitForOutputBuffer(int32_t picture_buffer_id, int count); | |
| 301 | |
| 302 // Initializes the DX11 Video format converter media types. | |
| 303 // Returns true on success. | |
| 304 bool InitializeDX11VideoFormatConverterMediaType(int width, int height); | |
| 305 | |
| 306 // Returns the output video frame dimensions (width, height). | |
| 307 // |sample| :- This is the output sample containing the video frame. | |
| 308 // |width| :- The width is returned here. | |
| 309 // |height| :- The height is returned here. | |
| 310 // Returns true on success. | |
| 311 bool GetVideoFrameDimensions(IMFSample* sample, int* width, int* height); | |
| 312 | |
| 313 // Sets the output type on the |transform| to the GUID identified by the | |
| 314 // the |output_type| parameter. The GUID can be MFVideoFormat_RGB32, | |
| 315 // MFVideoFormat_ARGB32, MFVideoFormat_NV12, etc. | |
| 316 // Additionally if the |width| and |height| parameters are non zero, then | |
| 317 // this function also sets the MF_MT_FRAME_SIZE attribute on the type. | |
| 318 // Returns true on success. | |
| 319 bool SetTransformOutputType(IMFTransform* transform, | |
| 320 const GUID& output_type, | |
| 321 int width, | |
| 322 int height); | |
| 323 | |
| 324 // Checks if the resolution, bitrate etc of the stream changed. We do this | |
| 325 // by keeping track of the SPS/PPS frames and if they change we assume | |
| 326 // that the configuration changed. | |
| 327 // Returns S_OK or S_FALSE on succcess. | |
| 328 // The |config_changed| parameter is set to true if we detect a change in the | |
| 329 // stream. | |
| 330 HRESULT CheckConfigChanged(IMFSample* sample, bool* config_changed); | |
| 331 | |
| 332 // Called when we detect a stream configuration change. We reinitialize the | |
| 333 // decoder here. | |
| 334 void ConfigChanged(const Config& config); | |
| 335 | |
| 336 // To expose client callbacks from VideoDecodeAccelerator. | |
| 337 media::VideoDecodeAccelerator::Client* client_; | |
| 338 | |
| 339 base::win::ScopedComPtr<IMFTransform> decoder_; | |
| 340 base::win::ScopedComPtr<IMFTransform> video_format_converter_mft_; | |
| 341 | |
| 342 base::win::ScopedComPtr<IDirect3D9Ex> d3d9_; | |
| 343 base::win::ScopedComPtr<IDirect3DDevice9Ex> d3d9_device_ex_; | |
| 344 base::win::ScopedComPtr<IDirect3DDeviceManager9> device_manager_; | |
| 345 base::win::ScopedComPtr<IDirect3DQuery9> query_; | |
| 346 | |
| 347 base::win::ScopedComPtr<ID3D11Device > d3d11_device_; | |
| 348 base::win::ScopedComPtr<IMFDXGIDeviceManager> d3d11_device_manager_; | |
| 349 base::win::ScopedComPtr<ID3D10Multithread> multi_threaded_; | |
| 350 base::win::ScopedComPtr<ID3D11DeviceContext> d3d11_device_context_; | |
| 351 base::win::ScopedComPtr<ID3D11Query> d3d11_query_; | |
| 352 | |
| 353 // Ideally the reset token would be a stack variable which is used while | |
| 354 // creating the device manager. However it seems that the device manager | |
| 355 // holds onto the token and attempts to access it if the underlying device | |
| 356 // changes. | |
| 357 // TODO(ananta): This needs to be verified. | |
| 358 uint32_t dev_manager_reset_token_; | |
| 359 | |
| 360 // Reset token for the DX11 device manager. | |
| 361 uint32_t dx11_dev_manager_reset_token_; | |
| 362 | |
| 363 uint32_t dx11_dev_manager_reset_token_format_conversion_; | |
| 364 | |
| 365 // The EGL config to use for decoded frames. | |
| 366 EGLConfig egl_config_; | |
| 367 | |
| 368 // Current state of the decoder. | |
| 369 volatile State state_; | |
| 370 | |
| 371 MFT_INPUT_STREAM_INFO input_stream_info_; | |
| 372 MFT_OUTPUT_STREAM_INFO output_stream_info_; | |
| 373 | |
| 374 // Contains information about a decoded sample. | |
| 375 struct PendingSampleInfo { | |
| 376 PendingSampleInfo(int32_t buffer_id, IMFSample* sample); | |
| 377 PendingSampleInfo(const PendingSampleInfo& other); | |
| 378 ~PendingSampleInfo(); | |
| 379 | |
| 380 int32_t input_buffer_id; | |
| 381 | |
| 382 // The target picture buffer id where the frame would be copied to. | |
| 383 // Defaults to -1. | |
| 384 int picture_buffer_id; | |
| 385 | |
| 386 base::win::ScopedComPtr<IMFSample> output_sample; | |
| 387 }; | |
| 388 | |
| 389 typedef std::list<PendingSampleInfo> PendingOutputSamples; | |
| 390 | |
| 391 // List of decoded output samples. Protected by |decoder_lock_|. | |
| 392 PendingOutputSamples pending_output_samples_; | |
| 393 | |
| 394 // This map maintains the picture buffers passed the client for decoding. | |
| 395 // The key is the picture buffer id. | |
| 396 OutputBuffers output_picture_buffers_; | |
| 397 | |
| 398 // After a resolution change there may be a few output buffers which have yet | |
| 399 // to be displayed so they cannot be dismissed immediately. We move them from | |
| 400 // |output_picture_buffers_| to this map so they may be dismissed once they | |
| 401 // become available. | |
| 402 OutputBuffers stale_output_picture_buffers_; | |
| 403 | |
| 404 // Set to true if we requested picture slots from the client. | |
| 405 bool pictures_requested_; | |
| 406 | |
| 407 // Counter which holds the number of input packets before a successful | |
| 408 // decode. | |
| 409 int inputs_before_decode_; | |
| 410 | |
| 411 // Set to true when the drain message is sent to the decoder during a flush | |
| 412 // operation. Used to ensure the message is only sent once after | |
| 413 // |pending_input_buffers_| is drained. Protected by |decoder_lock_|. | |
| 414 bool sent_drain_message_; | |
| 415 | |
| 416 // List of input samples waiting to be processed. | |
| 417 typedef std::list<base::win::ScopedComPtr<IMFSample>> PendingInputs; | |
| 418 PendingInputs pending_input_buffers_; | |
| 419 | |
| 420 // Callback to get current GLContext. | |
| 421 GetGLContextCallback get_gl_context_cb_; | |
| 422 // Callback to set the correct gl context. | |
| 423 MakeGLContextCurrentCallback make_context_current_cb_; | |
| 424 | |
| 425 // Which codec we are decoding with hardware acceleration. | |
| 426 media::VideoCodec codec_; | |
| 427 // Thread on which the decoder operations like passing input frames, | |
| 428 // getting output frames are performed. One instance of this thread | |
| 429 // is created per decoder instance. | |
| 430 base::Thread decoder_thread_; | |
| 431 | |
| 432 // Task runner to be used for posting tasks to the decoder thread. | |
| 433 scoped_refptr<base::SingleThreadTaskRunner> decoder_thread_task_runner_; | |
| 434 | |
| 435 // Task runner to be used for posting tasks to the main thread. | |
| 436 scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_; | |
| 437 | |
| 438 // Used to synchronize access between the decoder thread and the main thread. | |
| 439 base::Lock decoder_lock_; | |
| 440 | |
| 441 // Disallow rebinding WeakReference ownership to a different thread by | |
| 442 // keeping a persistent reference. This avoids problems with the | |
| 443 // thread safety of reaching into this class from multiple threads to | |
| 444 // attain a WeakPtr. | |
| 445 base::WeakPtr<DXVAVideoDecodeAccelerator> weak_ptr_; | |
| 446 | |
| 447 // Set to true if we are in the context of a Flush operation. Used to prevent | |
| 448 // multiple flush done notifications being sent out. | |
| 449 bool pending_flush_; | |
| 450 | |
| 451 // Defaults to false. Indicates if we should use D3D or DX11 interfaces for | |
| 452 // H/W decoding. | |
| 453 bool use_dx11_; | |
| 454 | |
| 455 // True if we should use DXGI keyed mutexes to synchronize between the two | |
| 456 // contexts. | |
| 457 bool use_keyed_mutex_; | |
| 458 | |
| 459 // Set to true if the DX11 video format converter input media types need to | |
| 460 // be initialized. Defaults to true. | |
| 461 bool dx11_video_format_converter_media_type_needs_init_; | |
| 462 | |
| 463 // Set to true if we are sharing ANGLE's device. | |
| 464 bool using_angle_device_; | |
| 465 | |
| 466 // Enables experimental hardware acceleration for VP8/VP9 video decoding. | |
| 467 const bool enable_accelerated_vpx_decode_; | |
| 468 | |
| 469 // The media foundation H.264 decoder has problems handling changes like | |
| 470 // resolution change, bitrate change etc. If we reinitialize the decoder | |
| 471 // when these changes occur then, the decoder works fine. The | |
| 472 // H264ConfigChangeDetector class provides functionality to check if the | |
| 473 // stream configuration changed. | |
| 474 std::unique_ptr<H264ConfigChangeDetector> config_change_detector_; | |
| 475 | |
| 476 // Contains the initialization parameters for the video. | |
| 477 Config config_; | |
| 478 | |
| 479 // WeakPtrFactory for posting tasks back to |this|. | |
| 480 base::WeakPtrFactory<DXVAVideoDecodeAccelerator> weak_this_factory_; | |
| 481 | |
| 482 // Function pointer for the MFCreateDXGIDeviceManager API. | |
| 483 static CreateDXGIDeviceManager create_dxgi_device_manager_; | |
| 484 | |
| 485 DISALLOW_COPY_AND_ASSIGN(DXVAVideoDecodeAccelerator); | |
| 486 }; | |
| 487 | |
| 488 } // namespace content | |
| 489 | |
| 490 #endif // CONTENT_COMMON_GPU_MEDIA_DXVA_VIDEO_DECODE_ACCELERATOR_H_ | |
| OLD | NEW |